• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

daisytuner / docc / 22062940668

16 Feb 2026 12:34PM UTC coverage: 66.445% (+0.1%) from 66.315%
22062940668

Pull #512

github

web-flow
Merge ebb808d27 into 81beb11c5
Pull Request #512: Integrate python docc frontends

3 of 5 new or added lines in 3 files covered. (60.0%)

174 existing lines in 4 files now uncovered.

23582 of 35491 relevant lines covered (66.45%)

375.3 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.03
/python/docc/python/python_program.py
1
import inspect
4✔
2
import shutil
4✔
3
import textwrap
4✔
4
import ast
4✔
5
import os
4✔
6
import getpass
4✔
7
import hashlib
4✔
8
import numpy as np
4✔
9
from typing import Annotated, get_origin, get_args, Any, Optional
4✔
10

11
from docc.sdfg import (
4✔
12
    Scalar,
13
    PrimitiveType,
14
    Pointer,
15
    Structure,
16
    Array,
17
    Type,
18
    StructuredSDFG,
19
    StructuredSDFGBuilder,
20
)
21
from docc.compiler.docc_program import DoccProgram
4✔
22
from docc.compiler.compiled_sdfg import CompiledSDFG
4✔
23
from docc.python.ast_parser import ASTParser
4✔
24

25

26
def _compile_wrapper(self, output_folder=None):
4✔
27
    """Wrapper to allow StructuredSDFG.compile() to return a CompiledSDFG."""
28
    lib_path = self._compile(output_folder)
×
29
    return CompiledSDFG(lib_path, self)
×
30

31

32
# Monkey-patch StructuredSDFG to add compile method
33
StructuredSDFG.compile = _compile_wrapper
4✔
34

35

36
def _map_python_type(dtype):
4✔
37
    """Map Python/numpy types to SDFG types."""
38
    # If it is already a sdfg Type, return it
39
    if isinstance(dtype, Type):
4✔
40
        return dtype
×
41

42
    # Handle Annotated for Arrays
43
    if get_origin(dtype) is Annotated:
4✔
44
        args = get_args(dtype)
4✔
45
        base_type = args[0]
4✔
46
        metadata = args[1:]
4✔
47

48
        if base_type is np.ndarray:
4✔
49
            # Convention: Annotated[np.ndarray, shape, dtype]
50
            shape = metadata[0]
4✔
51
            elem_type = Scalar(PrimitiveType.Double)  # Default
4✔
52

53
            if len(metadata) > 1:
4✔
54
                possible_dtype = metadata[1]
4✔
55
                elem_type = _map_python_type(possible_dtype)
4✔
56

57
            return Pointer(elem_type)
4✔
58

59
    # Handle numpy.ndarray[Shape, DType]
60
    if get_origin(dtype) is np.ndarray:
4✔
61
        args = get_args(dtype)
×
62
        # args[0] is shape, args[1] is dtype
63
        if len(args) >= 2:
×
64
            elem_type = _map_python_type(args[1])
×
65
            return Pointer(elem_type)
×
66

67
    # Simple mapping for python types
68
    if dtype is float or dtype is np.float64:
4✔
69
        return Scalar(PrimitiveType.Double)
4✔
70
    elif dtype is np.float32:
4✔
71
        return Scalar(PrimitiveType.Float)
×
72
    elif dtype is bool or dtype is np.bool_:
4✔
73
        return Scalar(PrimitiveType.Bool)
4✔
74
    elif dtype is int or dtype is np.int64:
4✔
75
        return Scalar(PrimitiveType.Int64)
4✔
76
    elif dtype is np.int32:
4✔
77
        return Scalar(PrimitiveType.Int32)
4✔
78
    elif dtype is np.int16:
4✔
79
        return Scalar(PrimitiveType.Int16)
×
80
    elif dtype is np.int8:
4✔
81
        return Scalar(PrimitiveType.Int8)
×
82
    elif dtype is np.uint64:
4✔
83
        return Scalar(PrimitiveType.UInt64)
×
84
    elif dtype is np.uint32:
4✔
85
        return Scalar(PrimitiveType.UInt32)
×
86
    elif dtype is np.uint16:
4✔
87
        return Scalar(PrimitiveType.UInt16)
×
88
    elif dtype is np.uint8:
4✔
89
        return Scalar(PrimitiveType.UInt8)
×
90

91
    # Handle Python classes - map to Structure type
92
    if inspect.isclass(dtype):
4✔
93
        # Use the class name as the structure name
94
        return Pointer(Structure(dtype.__name__))
4✔
95

96
    return dtype
×
97

98

99
class PythonProgram(DoccProgram):
4✔
100

101
    def __init__(
4✔
102
        self,
103
        func,
104
        target: str = "none",
105
        category: str = "server",
106
        instrumentation_mode: Optional[str] = None,
107
        capture_args: Optional[bool] = None,
108
        remote_tuning: bool = False,
109
    ):
110
        super().__init__(
4✔
111
            name=func.__name__,
112
            target=target,
113
            category=category,
114
            instrumentation_mode=instrumentation_mode,
115
            capture_args=capture_args,
116
            remote_tuning=remote_tuning,
117
        )
118
        self.func = func
4✔
119
        self._last_structure_member_info = {}
4✔
120

121
    def __call__(self, *args: Any) -> Any:
4✔
122
        # JIT compile and run
123
        compiled = self.compile(*args)
4✔
124
        res = compiled(*args)
4✔
125

126
        # Handle return value conversion based on annotation
127
        sig = inspect.signature(self.func)
4✔
128
        ret_annotation = sig.return_annotation
4✔
129

130
        if ret_annotation is not inspect.Signature.empty:
4✔
131
            if get_origin(ret_annotation) is Annotated:
4✔
132
                type_args = get_args(ret_annotation)
4✔
133
                if len(type_args) >= 1 and type_args[0] is np.ndarray:
4✔
134
                    shape = None
4✔
135
                    if len(type_args) >= 2:
4✔
136
                        shape = type_args[1]
4✔
137

138
                    if shape is not None:
4✔
139
                        try:
4✔
140
                            return np.ctypeslib.as_array(res, shape=shape)
4✔
141
                        except Exception:
×
142
                            pass
×
143

144
        # Try to infer return shape from metadata
145
        if hasattr(compiled, "get_return_shape"):
4✔
146
            shape = compiled.get_return_shape(*args)
4✔
147
            if shape is not None:
4✔
148
                try:
×
149
                    return np.ctypeslib.as_array(res, shape=shape)
×
150
                except Exception:
×
151
                    pass
×
152

153
        return res
4✔
154

155
    def compile(
4✔
156
        self,
157
        *args: Any,
158
        output_folder: Optional[str] = None,
159
        instrumentation_mode: Optional[str] = None,
160
        capture_args: Optional[bool] = None,
161
    ) -> CompiledSDFG:
162
        original_output_folder = output_folder
4✔
163

164
        # Resolve options
165
        if instrumentation_mode is None:
4✔
166
            instrumentation_mode = self.instrumentation_mode
4✔
167
        if capture_args is None:
4✔
168
            capture_args = self.capture_args
4✔
169

170
        # Check environment variable DOCC_CI
171
        docc_ci = os.environ.get("DOCC_CI", "")
4✔
172
        if docc_ci:
4✔
173
            if docc_ci == "regions":
×
174
                if instrumentation_mode is None:
×
175
                    instrumentation_mode = "ols"
×
176
            elif docc_ci == "arg-capture":
×
177
                if capture_args is None:
×
178
                    capture_args = True
×
179
            else:
180
                # Full mode (or unknown value treated as full)
181
                if instrumentation_mode is None:
×
182
                    instrumentation_mode = "ols"
×
183
                if capture_args is None:
×
184
                    capture_args = True
×
185

186
        # Defaults
187
        if instrumentation_mode is None:
4✔
188
            instrumentation_mode = ""
4✔
189
        if capture_args is None:
4✔
190
            capture_args = False
4✔
191

192
        # 1. Analyze arguments and shapes
193
        arg_types = []
4✔
194
        shape_values = []  # List of unique shape values found
4✔
195
        shape_sources = []  # List of (arg_idx, dim_idx) for each unique shape value
4✔
196

197
        # Mapping from (arg_idx, dim_idx) -> unique_shape_idx
198
        arg_shape_mapping = {}
4✔
199

200
        # First pass: collect scalar integer arguments and their values
201
        sig = inspect.signature(self.func)
4✔
202
        params = list(sig.parameters.items())
4✔
203
        scalar_int_params = {}  # Maps value -> parameter name (first one wins)
4✔
204
        for i, ((name, param), arg) in enumerate(zip(params, args)):
4✔
205
            if isinstance(arg, (int, np.integer)) and not isinstance(
4✔
206
                arg, (bool, np.bool_)
207
            ):
208
                val = int(arg)
4✔
209
                if val not in scalar_int_params:
4✔
210
                    scalar_int_params[val] = name
4✔
211

212
        for i, arg in enumerate(args):
4✔
213
            t = self._infer_type(arg)
4✔
214
            arg_types.append(t)
4✔
215

216
            if isinstance(arg, np.ndarray):
4✔
217
                for dim_idx, dim_val in enumerate(arg.shape):
4✔
218
                    # Check if we've seen this value
219
                    if dim_val in shape_values:
4✔
220
                        # Reuse
221
                        u_idx = shape_values.index(dim_val)
4✔
222
                    else:
223
                        # New
224
                        u_idx = len(shape_values)
4✔
225
                        shape_values.append(dim_val)
4✔
226
                        shape_sources.append((i, dim_idx))
4✔
227

228
                    arg_shape_mapping[(i, dim_idx)] = u_idx
4✔
229

230
        # Detect scalar-shape equivalences: which shape indices have a matching scalar param
231
        # Maps unique_shape_idx -> scalar parameter name
232
        shape_to_scalar = {}
4✔
233
        for s_idx, s_val in enumerate(shape_values):
4✔
234
            if s_val in scalar_int_params:
4✔
235
                shape_to_scalar[s_idx] = scalar_int_params[s_val]
4✔
236

237
        # 2. Signature - include scalar-shape equivalences for correct caching
238
        mapping_sig = sorted(arg_shape_mapping.items())
4✔
239
        equiv_sig = sorted(shape_to_scalar.items())
4✔
240
        type_sig = ", ".join(self._type_to_str(t) for t in arg_types)
4✔
241
        signature = f"{type_sig}|{mapping_sig}|{equiv_sig}"
4✔
242

243
        if output_folder is None:
4✔
244
            filename = inspect.getsourcefile(self.func)
4✔
245
            hash_input = f"{filename}|{self.name}|{self.target}|{self.category}|{self.capture_args}|{self.instrumentation_mode}|{signature}".encode(
4✔
246
                "utf-8"
247
            )
248
            stable_id = hashlib.sha256(hash_input).hexdigest()[:16]
4✔
249

250
            docc_tmp = os.environ.get("DOCC_TMP")
4✔
251
            if docc_tmp:
4✔
252
                output_folder = f"{docc_tmp}/{self.name}-{stable_id}"
×
253
            else:
254
                user = os.getenv("USER")
4✔
255
                if not user:
4✔
256
                    user = getpass.getuser()
4✔
257
                output_folder = f"/tmp/{user}/DOCC/{self.name}-{stable_id}"
4✔
258

259
        if original_output_folder is None and signature in self.cache:
4✔
260
            return self.cache[signature]
4✔
261

262
        # 3. Build SDFG
263
        if os.path.exists(output_folder):
4✔
264
            # Multiple python processes running the same code?
265
            shutil.rmtree(output_folder)
4✔
266
        sdfg, out_args, out_shapes = self._build_sdfg(
4✔
267
            arg_types, args, arg_shape_mapping, len(shape_values), shape_to_scalar
268
        )
269
        sdfg.validate()
4✔
270

271
        # Tensor targets keep tensor nodes
272
        if self.target != "onnx":
4✔
273
            sdfg.expand()
4✔
274

275
        # Simplify pipelines
276
        sdfg.simplify()
4✔
277

278
        # Normalization for scheduling
279
        if self.target != "none":
4✔
280
            sdfg.normalize()
4✔
281

282
        sdfg.dump(output_folder)
4✔
283

284
        # Schedule if target is specified
285
        if self.target != "none":
4✔
286
            sdfg.schedule(self.target, self.category, self.remote_tuning)
4✔
287

288
        self.last_sdfg = sdfg
4✔
289

290
        lib_path = sdfg._compile(
4✔
291
            output_folder=output_folder,
292
            target=self.target,
293
            instrumentation_mode=instrumentation_mode,
294
            capture_args=capture_args,
295
        )
296

297
        # Build ONNX model from JSON if target is onnx (after _compile creates the JSON)
298
        if self.target == "onnx":
4✔
299
            from docc.python.onnx_model_builder import convert_json_to_onnx
4✔
300

301
            onnx_model_path = convert_json_to_onnx(output_folder)
4✔
302
            if onnx_model_path:
4✔
303
                print(f"Generated ONNX models: {onnx_model_path}")
4✔
304

305
        # 5. Create CompiledSDFG
306
        compiled = CompiledSDFG(
4✔
307
            lib_path,
308
            sdfg,
309
            shape_sources,
310
            self._last_structure_member_info,
311
            out_args,
312
            out_shapes,
313
        )
314

315
        # Cache if using default output folder
316
        if original_output_folder is None:
4✔
317
            self.cache[signature] = compiled
4✔
318

319
        return compiled
4✔
320

321
    def to_sdfg(self, *args: Any) -> StructuredSDFG:
4✔
UNCOV
322
        arg_types = [self._infer_type(arg) for arg in args]
×
323

324
        # Build shape mapping
325
        shape_values = []
×
UNCOV
326
        shape_sources = []
×
327
        arg_shape_mapping = {}
×
328

329
        sig = inspect.signature(self.func)
×
330
        params = list(sig.parameters.items())
×
331
        scalar_int_params = {}
×
UNCOV
332
        for i, ((name, param), arg) in enumerate(zip(params, args)):
×
UNCOV
333
            if isinstance(arg, (int, np.integer)) and not isinstance(
×
334
                arg, (bool, np.bool_)
335
            ):
336
                val = int(arg)
×
UNCOV
337
                if val not in scalar_int_params:
×
338
                    scalar_int_params[val] = name
×
339

340
        for i, arg in enumerate(args):
×
341
            if isinstance(arg, np.ndarray):
×
342
                for dim_idx, dim_val in enumerate(arg.shape):
×
UNCOV
343
                    if dim_val in shape_values:
×
344
                        u_idx = shape_values.index(dim_val)
×
345
                    else:
346
                        u_idx = len(shape_values)
×
347
                        shape_values.append(dim_val)
×
UNCOV
348
                        shape_sources.append((i, dim_idx))
×
349
                    arg_shape_mapping[(i, dim_idx)] = u_idx
×
350

351
        shape_to_scalar = {}
×
352
        for s_idx, s_val in enumerate(shape_values):
×
UNCOV
353
            if s_val in scalar_int_params:
×
354
                shape_to_scalar[s_idx] = scalar_int_params[s_val]
×
355

UNCOV
356
        sdfg, _, _ = self._build_sdfg(
×
357
            arg_types, args, arg_shape_mapping, len(shape_values), shape_to_scalar
358
        )
UNCOV
359
        return sdfg
×
360

361
    def _convert_inputs(self, args: tuple) -> tuple:
4✔
UNCOV
362
        return args
×
363

364
    def _convert_outputs(self, result: Any, original_args: tuple) -> Any:
4✔
UNCOV
365
        return result
×
366

367
    def _get_signature(self, arg_types):
4✔
UNCOV
368
        return ", ".join(self._type_to_str(t) for t in arg_types)
×
369

370
    def _type_to_str(self, t):
4✔
371
        if isinstance(t, Scalar):
4✔
372
            return f"Scalar({t.primitive_type})"
4✔
373
        elif isinstance(t, Array):
4✔
UNCOV
374
            return f"Array({self._type_to_str(t.element_type)}, {t.num_elements})"
×
375
        elif isinstance(t, Pointer):
4✔
376
            return f"Pointer({self._type_to_str(t.pointee_type)})"
4✔
377
        elif isinstance(t, Structure):
4✔
378
            return f"Structure({t.name})"
4✔
UNCOV
379
        return str(t)
×
380

381
    def _infer_type(self, arg):
4✔
382
        if isinstance(arg, (float, np.float64)):
4✔
383
            return Scalar(PrimitiveType.Double)
4✔
384
        elif isinstance(arg, np.float32):
4✔
385
            return Scalar(PrimitiveType.Float)
4✔
386
        elif isinstance(arg, (bool, np.bool_)):
4✔
387
            return Scalar(PrimitiveType.Bool)
4✔
388
        elif isinstance(arg, (int, np.int64)):
4✔
389
            return Scalar(PrimitiveType.Int64)
4✔
390
        elif isinstance(arg, np.int32):
4✔
391
            return Scalar(PrimitiveType.Int32)
4✔
392
        elif isinstance(arg, np.int16):
4✔
393
            return Scalar(PrimitiveType.Int16)
×
394
        elif isinstance(arg, np.int8):
4✔
395
            return Scalar(PrimitiveType.Int8)
×
396
        elif isinstance(arg, np.uint64):
4✔
397
            return Scalar(PrimitiveType.UInt64)
×
398
        elif isinstance(arg, np.uint32):
4✔
399
            return Scalar(PrimitiveType.UInt32)
×
400
        elif isinstance(arg, np.uint16):
4✔
401
            return Scalar(PrimitiveType.UInt16)
×
402
        elif isinstance(arg, np.uint8):
4✔
UNCOV
403
            return Scalar(PrimitiveType.UInt8)
×
404
        elif isinstance(arg, np.ndarray):
4✔
405
            # Map dtype
406
            if arg.dtype == np.float64:
4✔
407
                elem_type = Scalar(PrimitiveType.Double)
4✔
408
            elif arg.dtype == np.float32:
4✔
409
                elem_type = Scalar(PrimitiveType.Float)
4✔
410
            elif arg.dtype == np.bool_:
4✔
411
                elem_type = Scalar(PrimitiveType.Bool)
4✔
412
            elif arg.dtype == np.int64:
4✔
413
                elem_type = Scalar(PrimitiveType.Int64)
4✔
414
            elif arg.dtype == np.int32:
4✔
415
                elem_type = Scalar(PrimitiveType.Int32)
4✔
416
            elif arg.dtype == np.int16:
×
417
                elem_type = Scalar(PrimitiveType.Int16)
×
418
            elif arg.dtype == np.int8:
×
419
                elem_type = Scalar(PrimitiveType.Int8)
×
420
            elif arg.dtype == np.uint64:
×
421
                elem_type = Scalar(PrimitiveType.UInt64)
×
422
            elif arg.dtype == np.uint32:
×
423
                elem_type = Scalar(PrimitiveType.UInt32)
×
424
            elif arg.dtype == np.uint16:
×
425
                elem_type = Scalar(PrimitiveType.UInt16)
×
UNCOV
426
            elif arg.dtype == np.uint8:
×
427
                elem_type = Scalar(PrimitiveType.UInt8)
×
428
            else:
UNCOV
429
                raise ValueError(f"Unsupported numpy dtype: {arg.dtype}")
×
430

431
            return Pointer(elem_type)
4✔
432
        elif isinstance(arg, str):
4✔
433
            # Explicitly reject strings - they are not supported
434
            raise ValueError(f"Unsupported argument type: {type(arg)}")
4✔
435
        else:
436
            # Check if it's a class instance
437
            if hasattr(arg, "__class__") and not isinstance(arg, type):
4✔
438
                # It's an instance of a class, return pointer to Structure
439
                return Pointer(Structure(arg.__class__.__name__))
4✔
UNCOV
440
            raise ValueError(f"Unsupported argument type: {type(arg)}")
×
441

442
    def _build_sdfg(
4✔
443
        self,
444
        arg_types,
445
        args,
446
        arg_shape_mapping,
447
        num_unique_shapes,
448
        shape_to_scalar=None,
449
    ):
450
        if shape_to_scalar is None:
4✔
451
            shape_to_scalar = {}
4✔
452
        sig = inspect.signature(self.func)
4✔
453

454
        # Handle return type - always void for SDFG, output args used for returns
455
        return_type = Scalar(PrimitiveType.Void)
4✔
456
        infer_return_type = True
4✔
457

458
        # Parse return annotation to determine output arguments if possible
459
        explicit_returns = []
4✔
460
        if sig.return_annotation is not inspect.Signature.empty:
4✔
461
            infer_return_type = False
4✔
462

463
            # Helper to normalize annotation to list of types
464
            def normalize_annotation(ann):
4✔
465
                # Handle Tuple[type, ...]
466
                origin = get_origin(ann)
4✔
467
                if origin is tuple:
4✔
468
                    type_args = get_args(ann)
×
469
                    # Tuple[()] or Tuple w/o args
UNCOV
470
                    if not type_args:
×
471
                        return []
×
472
                    # Tuple[int, float]
UNCOV
473
                    if len(type_args) > 0 and type_args[-1] is not Ellipsis:
×
UNCOV
474
                        return [_map_python_type(t) for t in type_args]
×
475
                    # Tuple[int, ...] - not supported for fixed number of returns yet?
476
                    # For now assume fixed tuple
UNCOV
477
                    return [_map_python_type(t) for t in type_args]
×
478
                else:
479
                    return [_map_python_type(ann)]
4✔
480

481
            explicit_returns = normalize_annotation(sig.return_annotation)
4✔
482
            for rt in explicit_returns:
4✔
483
                if not isinstance(rt, Type):
4✔
484
                    # Fallback if map failed (e.g. invalid annotation)
485
                    infer_return_type = True
×
UNCOV
486
                    explicit_returns = []
×
UNCOV
487
                    break
×
488

489
        builder = StructuredSDFGBuilder(f"{self.name}_sdfg", return_type)
4✔
490

491
        # Add pre-defined return arguments if we know them
492
        if not infer_return_type:
4✔
493
            for i, dtype in enumerate(explicit_returns):
4✔
494
                # Scalar -> Pointer(Scalar)
495
                # Array -> Already Pointer(Scalar). Keep it.
496
                arg_type = dtype
4✔
497
                if isinstance(dtype, Scalar):
4✔
498
                    arg_type = Pointer(dtype)
4✔
499

500
                builder.add_container(f"_docc_ret_{i}", arg_type, is_argument=True)
4✔
501

502
        # Register structure types for any class arguments
503
        # Also track member name to index mapping for each structure
504
        structures_to_register = {}
4✔
505
        structure_member_info = {}  # Maps struct_name -> {member_name: (index, type)}
4✔
506
        for i, (arg, dtype) in enumerate(zip(args, arg_types)):
4✔
507
            if isinstance(dtype, Pointer) and dtype.has_pointee_type():
4✔
508
                pointee = dtype.pointee_type
4✔
509
                if isinstance(pointee, Structure):
4✔
510
                    struct_name = pointee.name
4✔
511
                    if struct_name not in structures_to_register:
4✔
512
                        # Get class from arg to introspect members
513
                        if hasattr(arg, "__dict__"):
4✔
514
                            # Use __dict__ to get only instance attributes
515
                            # Sort by name to ensure consistent ordering
516
                            # Note: This alphabetical ordering is used to define the
517
                            # structure layout and must match the order expected by
518
                            # the backend code generation
519
                            member_types = []
4✔
520
                            member_names = []
4✔
521
                            for attr_name, attr_value in sorted(arg.__dict__.items()):
4✔
522
                                if not attr_name.startswith("_"):
4✔
523
                                    # Infer member type from instance attribute
524
                                    # Check bool before int since bool is subclass of int
525
                                    member_type = None
4✔
526
                                    if isinstance(attr_value, bool):
4✔
527
                                        member_type = Scalar(PrimitiveType.Bool)
×
528
                                    elif isinstance(attr_value, (int, np.int64)):
4✔
UNCOV
529
                                        member_type = Scalar(PrimitiveType.Int64)
×
530
                                    elif isinstance(attr_value, (float, np.float64)):
4✔
531
                                        member_type = Scalar(PrimitiveType.Double)
4✔
532
                                    elif isinstance(attr_value, np.int32):
×
533
                                        member_type = Scalar(PrimitiveType.Int32)
×
UNCOV
534
                                    elif isinstance(attr_value, np.float32):
×
UNCOV
535
                                        member_type = Scalar(PrimitiveType.Float)
×
536
                                    # TODO: Consider using np.integer and np.floating abstract types
537
                                    # for more comprehensive numpy type coverage
538
                                    # TODO: Add support for nested structures and arrays
539

540
                                    if member_type is not None:
4✔
541
                                        member_types.append(member_type)
4✔
542
                                        member_names.append(attr_name)
4✔
543

544
                            if member_types:
4✔
545
                                structures_to_register[struct_name] = member_types
4✔
546
                                # Build member name to (index, type) mapping
547
                                structure_member_info[struct_name] = {
4✔
548
                                    name: (idx, mtype)
549
                                    for idx, (name, mtype) in enumerate(
550
                                        zip(member_names, member_types)
551
                                    )
552
                                }
553

554
        # Store structure_member_info for later use in CompiledSDFG
555
        self._last_structure_member_info = structure_member_info
4✔
556

557
        # Register all discovered structures with the builder
558
        for struct_name, member_types in structures_to_register.items():
4✔
559
            builder.add_structure(struct_name, member_types)
4✔
560

561
        # Register arguments
562
        params = list(sig.parameters.items())
4✔
563
        if len(params) != len(arg_types):
4✔
UNCOV
564
            raise ValueError(
×
565
                f"Argument count mismatch: expected {len(params)}, got {len(arg_types)}"
566
            )
567

568
        array_info = {}
4✔
569

570
        # Add regular arguments
571
        for i, ((name, param), dtype, arg) in enumerate(zip(params, arg_types, args)):
4✔
572
            builder.add_container(name, dtype, is_argument=True)
4✔
573

574
            # If it's an array, prepare shape info
575
            if isinstance(arg, np.ndarray):
4✔
576
                shapes = []
4✔
577
                for dim_idx in range(arg.ndim):
4✔
578
                    u_idx = arg_shape_mapping[(i, dim_idx)]
4✔
579
                    # Use scalar parameter name if there's an equivalence, otherwise _sX
580
                    if u_idx in shape_to_scalar:
4✔
581
                        shapes.append(shape_to_scalar[u_idx])
4✔
582
                    else:
583
                        shapes.append(f"_s{u_idx}")
4✔
584

585
                array_info[name] = {"ndim": arg.ndim, "shapes": shapes}
4✔
586

587
        # Add unified shape arguments only for shapes without scalar equivalents
588
        for i in range(num_unique_shapes):
4✔
589
            if i not in shape_to_scalar:
4✔
590
                builder.add_container(
4✔
591
                    f"_s{i}", Scalar(PrimitiveType.Int64), is_argument=True
592
                )
593

594
        # Create symbol table for parser
595
        symbol_table = {}
4✔
596
        for i, ((name, param), dtype, arg) in enumerate(zip(params, arg_types, args)):
4✔
597
            symbol_table[name] = dtype
4✔
598

599
        for i in range(num_unique_shapes):
4✔
600
            if i not in shape_to_scalar:
4✔
601
                symbol_table[f"_s{i}"] = Scalar(PrimitiveType.Int64)
4✔
602

603
        # Parse AST
604
        source_lines, start_line = inspect.getsourcelines(self.func)
4✔
605
        source = textwrap.dedent("".join(source_lines))
4✔
606
        tree = ast.parse(source)
4✔
607
        ast.increment_lineno(tree, start_line - 1)
4✔
608
        func_def = tree.body[0]
4✔
609

610
        filename = inspect.getsourcefile(self.func)
4✔
611
        function_name = self.func.__name__
4✔
612

613
        parser = ASTParser(
4✔
614
            builder,
615
            array_info,
616
            symbol_table,
617
            filename,
618
            function_name,
619
            infer_return_type=infer_return_type,
620
            globals_dict=self.func.__globals__,
621
            structure_member_info=structure_member_info,
622
        )
623
        for node in func_def.body:
4✔
624
            parser.visit(node)
4✔
625

626
        sdfg = builder.move()
4✔
627
        # Mark return arguments metadata
628
        out_args = []
4✔
629
        for name in sdfg.arguments:
4✔
630
            if name.startswith("_docc_ret_"):
4✔
631
                out_args.append(name)
4✔
632

633
        return sdfg, out_args, parser.captured_return_shapes
4✔
634

635

636
def native(
4✔
637
    func=None,
638
    *,
639
    target="none",
640
    category="desktop",
641
    instrumentation_mode=None,
642
    capture_args=None,
643
):
644
    """Decorator to create a PythonProgram from a Python function.
645

646
    Example:
647
        @native
648
        def my_function(x: np.ndarray) -> np.ndarray:
649
            return x * 2
650

651
        result = my_function(np.array([1.0, 2.0, 3.0]))
652
    """
653
    if func is None:
4✔
654
        return lambda f: PythonProgram(
4✔
655
            f,
656
            target=target,
657
            category=category,
658
            instrumentation_mode=instrumentation_mode,
659
            capture_args=capture_args,
660
        )
661
    return PythonProgram(
4✔
662
        func,
663
        target=target,
664
        category=category,
665
        instrumentation_mode=instrumentation_mode,
666
        capture_args=capture_args,
667
    )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc