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

daisytuner / sdfglib / 21112243720

18 Jan 2026 01:08PM UTC coverage: 64.188% (+0.03%) from 64.154%
21112243720

Pull #462

github

web-flow
Merge d6bf7a605 into 92e9cbdc3
Pull Request #462: adds syntax support for multi-assignments and np.empty_like

31 of 33 new or added lines in 2 files covered. (93.94%)

197 existing lines in 5 files now uncovered.

19497 of 30375 relevant lines covered (64.19%)

387.69 hits per line

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

86.32
/python/docc/compiled_sdfg.py
1
import ctypes
3✔
2
from ._sdfg import Scalar, Array, Pointer, Structure, PrimitiveType
3✔
3

4
try:
3✔
5
    import numpy as np
3✔
6
except ImportError:
×
7
    np = None
×
8

9
_CTYPES_MAP = {
3✔
10
    PrimitiveType.Bool: ctypes.c_bool,
11
    PrimitiveType.Int8: ctypes.c_int8,
12
    PrimitiveType.Int16: ctypes.c_int16,
13
    PrimitiveType.Int32: ctypes.c_int32,
14
    PrimitiveType.Int64: ctypes.c_int64,
15
    PrimitiveType.UInt8: ctypes.c_uint8,
16
    PrimitiveType.UInt16: ctypes.c_uint16,
17
    PrimitiveType.UInt32: ctypes.c_uint32,
18
    PrimitiveType.UInt64: ctypes.c_uint64,
19
    PrimitiveType.Float: ctypes.c_float,
20
    PrimitiveType.Double: ctypes.c_double,
21
}
22

23

24
class CompiledSDFG:
3✔
25
    def __init__(self, lib_path, sdfg, shape_sources=None, structure_member_info=None):
3✔
26
        self.lib_path = lib_path
3✔
27
        self.sdfg = sdfg
3✔
28
        self.shape_sources = shape_sources or []
3✔
29
        self.structure_member_info = structure_member_info or {}
3✔
30
        self.lib = ctypes.CDLL(lib_path)
3✔
31
        self.func = getattr(self.lib, sdfg.name)
3✔
32

33
        # Cache for ctypes structure definitions
34
        self._ctypes_structures = {}
3✔
35

36
        # Set up argument types
37
        self.arg_types = []
3✔
38
        self.arg_sdfg_types = []  # Keep track of original sdfg types
3✔
39
        for arg_name in sdfg.arguments:
3✔
40
            arg_type = sdfg.type(arg_name)
3✔
41
            self.arg_sdfg_types.append(arg_type)
3✔
42
            ct_type = self._get_ctypes_type(arg_type)
3✔
43
            self.arg_types.append(ct_type)
3✔
44

45
        self.func.argtypes = self.arg_types
3✔
46

47
        # Set up return type
48
        self.func.restype = self._get_ctypes_type(sdfg.return_type)
3✔
49

50
    def _create_ctypes_structure(self, struct_name):
3✔
51
        """Create a ctypes Structure class for the given structure name."""
52
        if struct_name in self._ctypes_structures:
3✔
UNCOV
53
            return self._ctypes_structures[struct_name]
×
54

55
        if struct_name not in self.structure_member_info:
3✔
UNCOV
56
            raise ValueError(f"Structure '{struct_name}' not found in member info")
×
57

58
        # Get member info: {member_name: (index, type)}
59
        members = self.structure_member_info[struct_name]
3✔
60
        # Sort by index to get correct order
61
        sorted_members = sorted(members.items(), key=lambda x: x[1][0])
3✔
62

63
        # Build _fields_ for ctypes.Structure
64
        fields = []
3✔
65
        for member_name, (index, member_type) in sorted_members:
3✔
66
            ct_type = self._get_ctypes_type(member_type)
3✔
67
            fields.append((member_name, ct_type))
3✔
68

69
        # Create the ctypes Structure class dynamically
70
        class CStructure(ctypes.Structure):
3✔
71
            _fields_ = fields
3✔
72

73
        self._ctypes_structures[struct_name] = CStructure
3✔
74
        return CStructure
3✔
75

76
    def _get_ctypes_type(self, sdfg_type):
3✔
77
        if isinstance(sdfg_type, Scalar):
3✔
78
            return _CTYPES_MAP.get(sdfg_type.primitive_type, ctypes.c_void_p)
3✔
79
        elif isinstance(sdfg_type, Array):
3✔
80
            # Arrays are passed as pointers
UNCOV
81
            elem_type = _CTYPES_MAP.get(sdfg_type.primitive_type, ctypes.c_void_p)
×
UNCOV
82
            return ctypes.POINTER(elem_type)
×
83
        elif isinstance(sdfg_type, Pointer):
3✔
84
            # Check if pointee is a Structure
85
            # Note: has_pointee_type() is guaranteed to exist on Pointer instances from C++ bindings
86
            if sdfg_type.has_pointee_type():
3✔
87
                pointee = sdfg_type.pointee_type
3✔
88
                if isinstance(pointee, Structure):
3✔
89
                    # Create ctypes structure and return pointer to it
90
                    struct_class = self._create_ctypes_structure(pointee.name)
3✔
91
                    return ctypes.POINTER(struct_class)
3✔
92
                elif isinstance(pointee, Scalar):
3✔
93
                    elem_type = _CTYPES_MAP.get(pointee.primitive_type, ctypes.c_void_p)
3✔
94
                    return ctypes.POINTER(elem_type)
3✔
UNCOV
95
            return ctypes.c_void_p
×
UNCOV
96
        return ctypes.c_void_p
×
97

98
    def __call__(self, *args):
3✔
99
        # Expand arguments (handle numpy arrays and their shapes)
100
        expanded_args = list(args)
3✔
101

102
        # Append unified shape arguments
103
        for arg_idx, dim_idx in self.shape_sources:
3✔
104
            arg = args[arg_idx]
3✔
105
            if np is not None and isinstance(arg, np.ndarray):
3✔
106
                expanded_args.append(arg.shape[dim_idx])
3✔
107
            else:
UNCOV
108
                raise ValueError(
×
109
                    f"Expected ndarray at index {arg_idx} for shape source"
110
                )
111

112
        if len(expanded_args) != len(self.arg_types):
3✔
113
            raise ValueError(
×
114
                f"Expected {len(self.arg_types)} arguments (including implicit shapes), got {len(expanded_args)}"
115
            )
116

117
        converted_args = []
3✔
118
        # Keep references to ctypes structures to prevent garbage collection
119
        structure_refs = []
3✔
120

121
        for i, arg in enumerate(expanded_args):
3✔
122
            target_type = self.arg_types[i]
3✔
123
            sdfg_type = self.arg_sdfg_types[i] if i < len(self.arg_sdfg_types) else None
3✔
124

125
            # Handle numpy arrays
126
            if np is not None and isinstance(arg, np.ndarray):
3✔
127
                # Check if it's a pointer type
128
                if hasattr(target_type, "contents"):  # It's a pointer
3✔
129
                    converted_args.append(arg.ctypes.data_as(target_type))
3✔
130
                else:
131
                    converted_args.append(arg)
×
132
            # Handle class instances (structures)
133
            # Note: has_pointee_type() is guaranteed on Pointer instances
134
            elif (
3✔
135
                sdfg_type
136
                and isinstance(sdfg_type, Pointer)
137
                and sdfg_type.has_pointee_type()
138
                and isinstance(sdfg_type.pointee_type, Structure)
139
            ):
140
                # Convert Python object to ctypes structure
141
                struct_name = sdfg_type.pointee_type.name
3✔
142
                struct_class = self._ctypes_structures.get(struct_name)
3✔
143

144
                # This should not happen if type setup was done correctly
145
                if struct_class is None:
3✔
146
                    raise RuntimeError(
×
147
                        f"Internal error: Structure '{struct_name}' was not created during type setup. "
148
                        f"This indicates a bug in the compilation process."
149
                    )
150

151
                # Validate the Python object has the required structure
152
                if not hasattr(arg, "__dict__"):
3✔
153
                    raise TypeError(
×
154
                        f"Expected object with attributes for structure '{struct_name}', "
155
                        f"but got {type(arg).__name__} without __dict__"
156
                    )
157

158
                # Get member info to know the order
159
                members = self.structure_member_info[struct_name]
3✔
160
                sorted_members = sorted(members.items(), key=lambda x: x[1][0])
3✔
161

162
                # Create ctypes structure instance with values from Python object
163
                struct_values = {}
3✔
164
                for member_name, (index, member_type) in sorted_members:
3✔
165
                    if hasattr(arg, member_name):
3✔
166
                        struct_values[member_name] = getattr(arg, member_name)
3✔
167
                    else:
168
                        raise ValueError(
×
169
                            f"Python object missing attribute '{member_name}' for structure '{struct_name}'"
170
                        )
171

172
                c_struct = struct_class(**struct_values)
3✔
173
                structure_refs.append(c_struct)  # Keep alive
3✔
174
                # Pass pointer to the structure
175
                converted_args.append(ctypes.pointer(c_struct))
3✔
176
            else:
177
                converted_args.append(arg)
3✔
178

179
        return self.func(*converted_args)
3✔
180

181
    def get_return_shape(self, *args):
3✔
182
        shape_str = self.sdfg.metadata("return_shape")
3✔
183
        if not shape_str:
3✔
184
            return None
3✔
185

186
        shape_exprs = shape_str.split(",")
3✔
187

188
        # We need to evaluate these expressions
189
        # They might contain _s0, _s1 etc.
190
        # We have shape_sources which maps (arg_idx, dim_idx) -> unique_shape_idx
191

192
        # Reconstruct shape values
193
        shape_values = {}
3✔
194
        for i, (arg_idx, dim_idx) in enumerate(self.shape_sources):
3✔
195
            arg = args[arg_idx]
3✔
196
            if np is not None and isinstance(arg, np.ndarray):
3✔
197
                val = arg.shape[dim_idx]
3✔
198
                shape_values[f"_s{i}"] = val
3✔
199

200
        # Add scalar arguments to shape_values
201
        # We assume the first len(args) arguments in sdfg.arguments correspond to the user arguments
202
        if hasattr(self.sdfg, "arguments"):
3✔
203
            for arg_name, arg_val in zip(self.sdfg.arguments, args):
3✔
204
                if isinstance(arg_val, (int, np.integer)):
3✔
205
                    shape_values[arg_name] = int(arg_val)
3✔
206

207
        evaluated_shape = []
3✔
208
        for expr in shape_exprs:
3✔
209
            # Simple evaluation using eval with shape_values
210
            # Warning: eval is unsafe, but here expressions come from our compiler
211
            try:
3✔
212
                val = eval(expr, {}, shape_values)
3✔
213
                evaluated_shape.append(int(val))
3✔
UNCOV
214
            except Exception:
×
UNCOV
215
                return None
×
216

217
        return tuple(evaluated_shape)
3✔
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