Peano
Loading...
Searching...
No Matches
SyntaxTree.py
Go to the documentation of this file.
1from abc import ABC, abstractmethod
2import os
3import copy
4from enum import Enum
5from operator import itemgetter
6
7# We need to track the context of the code generation to support MLIR
8class Context:
9
10 def __init__(self):
11 # A stack of blocks e.g. for loops where we can store hoisted MLIR operations
12 self._blocks = [ Context.Block() ]
13
14 def push_block(self):
15 self._blocks.append(Context.Block())
16
17 def pop_block(self):
18 if self._blocks:
19 return self._blocks.pop()
20 else:
21 return Context.Block()
22
23 def get_block(self):
24 if not self._blocks:
25 self.push_block()
26 return self._blocks[-1]
27
28 def clear_block(self):
29 if self._blocks:
30 self._blocks[-1] = Context.Block()
31
32 # We need to store block-level MLIR that has been hoisted and ensure we don't
33 # repeat casting operators for indices within a block e.g. loops
34 class Block:
35
36 def __init__(self):
37 # We use this to hoist MLIR operations out of array indexing, function calls etc.
38 self._mlir = []
39 # We use a set to store the ids that we have already processed within the block
40 self._ids = {}
41
42 def push_mlir(self):
43 self._mlir.append([])
44
45 def pop_mlir(self):
46 if self._mlir:
47 return self._mlir.pop()
48 else:
49 return []
50
51 def get_mlir(self):
52 if not self._mlir:
53 self.push_mlir()
54 return self._mlir[-1]
55
56 def mlir_append(self, op):
57 self.get_mlir().append(op)
58
59 # def add_id(self, id):
60 # self._ids.update(id)
61
62 # def remove_id(self, id):
63 # self._ids.remove(id)
64
65 # def get_id(self, id):
66 # return self._ids.get(id)
67
68 # def find_id(self, id):
69 # return id in self._ids
70
71
72
73class Node(ABC):
74 output_format = None
75 spaces_per_tab = 4
76 use_accelerator = False
77 use_memory_manager = False
78 mlir_id_count = 0 # We need a variable to keep track of the MLIR ids %0, %1 etc.
79 type_map = {}
80 struct_map = {}
81 context = Context()
82 target_variable_name = None # For tracking when we're generating a specific variable name
83 mlir_symbol_table = {} # Maps DSL names (e.g., "patchData.QOut") to MLIR variable names (e.g., "%output")
84 mlir_memref_types = {} # Maps MLIR variable names (e.g., "%output") to their memref types (e.g., "memref<?x?xf64>")
85
86 # Initialise the type, struct and function maps
87 # NOTE: Populated lazily after Type classes are defined (see __init_type_map below)
88 type_map.update({'void': 'void'}) # Will be replaced with TVoid() in Phase 2a
89 type_map.update({'int': 'i64'}) # Match TInteger class and C++ kernel expectations
90 type_map.update({'double': 'f64'}) # Will be replaced with TFloat() in Phase 2a
91 type_map.update({'double*': 'memref<?xf64>'})
92 type_map.update({'tarch::la::Vector<2, double>': 'memref<?xi64>'}) # Using memref instead of !llvm.ptr for standard indexing
93 type_map.update({'tarch::la::Vector<3, double>': 'memref<?xi64>'}) # Using memref instead of !llvm.ptr for standard indexing
94 type_map.update({'CellData<double, double>&': '!llvm.ptr'}) #'memref<?xstruct<f64, f64>>'})
95 type_map.update({'const FluxFunctor&': '(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()'})
96 type_map.update({'const SourceFunctor&': '(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, !llvm.ptr) -> ()'})
97 type_map.update({'const NonconservativeProductFunctor&': '(!llvm.ptr, !llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()'})
98 type_map.update({'const MaxEigenvalueFunctorInPlace&': '(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()'})
99 type_map.update({'const MaxEigenvalueFunctor&': '(!llvm.ptr, !llvm.ptr, !llvm.ptr, f64, f64, i32, !llvm.ptr) -> ()'})
100 type_map.update({'peano4::utils::LoopPlacement': 'i32'})
101 # Note: QIn, QOut, cellCentre, cellSize, t, dt, id, numberOfCells, etc. are populated from struct_map below
102
103 # Functor function MLIR signatures for individual function calls
104 type_map.update({'flux': '(memref<?xf64>, !llvm.ptr, !llvm.ptr, f64, f64, i32, memref<?xf64>) -> ()'})
105 type_map.update({'nonconservativeProduct': '(memref<?xf64>, memref<?xf64>, !llvm.ptr, !llvm.ptr, f64, f64, i32, memref<?xf64>) -> ()'})
106 type_map.update({'sourceTerm': '(memref<?xf64>, !llvm.ptr, !llvm.ptr, f64, f64, memref<?xf64>) -> ()'})
107 type_map.update({'volumetricSource': '(memref<?xf64>, !llvm.ptr, f64, memref<?xf64>) -> ()'})
108 type_map.update({'maxEigenvalue': '(memref<?xf64>, !llvm.ptr, !llvm.ptr, f64, f64, i32, memref<?xf64>) -> ()'})
109
110 # Local variable storage types (for runtime-allocated temporary variables)
111 type_map.update({'source': 'memref<?xmemref<?xf64>>'})
112 type_map.update({'maxEigenvaluesX': 'memref<?xmemref<?xf64>>'})
113 type_map.update({'maxEigenvaluesY': 'memref<?xmemref<?xf64>>'})
114 type_map.update({'maxEigenvaluesZ': 'memref<?xmemref<?xf64>>'})
115 type_map.update({'maxEigenvaluesX2': 'memref<?xmemref<?xf64>>'})
116 type_map.update({'maxEigenvaluesY2': 'memref<?xmemref<?xf64>>'})
117 type_map.update({'maxEigenvaluesZ2': 'memref<?xmemref<?xf64>>'})
118 type_map.update({'maxEigenvaluesXY': 'memref<?xmemref<?xf64>>'})
119 type_map.update({'maxEigenvaluesPerVolume': 'memref<?xmemref<?xf64>>'})
120
121 # Lambda/flux arrays (2D double** patterns) - These are ASSIGNED to with nested subscripts
122 # so they need nested memref type mapping
123 type_map.update({'lambdaLeft': 'memref<?xmemref<?xf64>>'})
124 type_map.update({'lambdaRight': 'memref<?xmemref<?xf64>>'})
125 type_map.update({'lambdaBottom': 'memref<?xmemref<?xf64>>'})
126 type_map.update({'lambdaTop': 'memref<?xmemref<?xf64>>'})
127 type_map.update({'lambdaNear': 'memref<?xmemref<?xf64>>'})
128 type_map.update({'lambdaFar': 'memref<?xmemref<?xf64>>'})
129 type_map.update({'fluxLeft': 'memref<?xmemref<?xf64>>'})
130 type_map.update({'fluxRight': 'memref<?xmemref<?xf64>>'})
131 type_map.update({'fluxBottom': 'memref<?xmemref<?xf64>>'})
132 type_map.update({'fluxTop': 'memref<?xmemref<?xf64>>'})
133 type_map.update({'fluxNear': 'memref<?xmemref<?xf64>>'})
134 type_map.update({'fluxFar': 'memref<?xmemref<?xf64>>'})
135
136 # Bridge arrays: Used as temporary storage, allocated with outer dimension then inner arrays
137 # Like lambdaLeft, these are semantically "array of arrays" in the C++ kernel
138 # Must use nested memref type to match the allocation pattern
139 type_map.update({'flux_x': 'memref<?xmemref<?xf64>>'})
140 type_map.update({'flux_y': 'memref<?xmemref<?xf64>>'})
141 type_map.update({'flux_z': 'memref<?xmemref<?xf64>>'})
142 type_map.update({'average_x': 'memref<?xmemref<?xf64>>'})
143 type_map.update({'average_y': 'memref<?xmemref<?xf64>>'})
144 type_map.update({'average_z': 'memref<?xmemref<?xf64>>'})
145 type_map.update({'delta_x': 'memref<?xmemref<?xf64>>'})
146 type_map.update({'delta_y': 'memref<?xmemref<?xf64>>'})
147 type_map.update({'delta_z': 'memref<?xmemref<?xf64>>'})
148 type_map.update({'nonconservative_x': 'memref<?xmemref<?xf64>>'})
149 type_map.update({'nonconservative_y': 'memref<?xmemref<?xf64>>'})
150 type_map.update({'nonconservative_z': 'memref<?xmemref<?xf64>>'})
151
152 # MLIR to C++ type mappings for function signature generation
153 type_map.update({'memref<?xf64>': 'void*'})
154 type_map.update({'!llvm.ptr': 'void*'})
155 type_map.update({'f64': 'double'})
156 type_map.update({'i32': 'int'})
157 type_map.update({'i64': 'long'})
158 type_map.update({'i1': 'bool'})
159
160 # MLIRCellData struct layout with MemRefDescriptor fields
161 descriptor2DType = "!llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)>"
162 descriptor1DType = "!llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>"
163
164 # Updated patchTypes: 7 descriptors + 3 i64 scalars + 2 ptrs = 12 fields
165 patchTypes = [descriptor2DType, descriptor2DType, descriptor1DType, descriptor1DType,
166 descriptor1DType, descriptor1DType, descriptor1DType, 'i64', 'i64', 'i64', 'ptr', 'ptr']
167
168 struct_map.update({'patchData': [
169 ('QIn' , f"{descriptor2DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 0: 2D descriptor
170 ('QOut' , f"{descriptor2DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 1: 2D descriptor
171 ('cellCentre' , f"{descriptor1DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 2: 1D descriptor
172 ('cellSize' , f"{descriptor1DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 3: 1D descriptor
173 ('t' , f"{descriptor1DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 4: 1D descriptor
174 ('dt' , f"{descriptor1DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 5: 1D descriptor
175 ('id' , f"{descriptor1DType}|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 6: 1D descriptor
176 ('numberOfCells' , f"i64|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 7: i64
177 ('memoryLocation' , f"i64|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 8: i64
178 ('targetDevice' , f"i64|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 9: i64
179 ('QOut_legacy' , f"!llvm.ptr|||!llvm.struct<({', '.join(patchTypes)})>"), # Field 10: function pointer
180 ('maxEigenvalue' , f"memref<?xf64>|||!llvm.struct<({', '.join(patchTypes)})>")]}) # Field 11: function pointer
181
182 # Populate type_map with descriptor field types from struct_map
183 # This maps field names to their converted MLIR memref types
184 for struct_name, fields in struct_map.items():
185 for field_name, field_info in fields:
186 target_type = field_info.split('|||')[0]
187 # Convert descriptor types to their memref equivalents
188 if target_type == descriptor2DType:
189 type_map.update({field_name: 'memref<?x?xf64>'})
190 elif target_type == descriptor1DType:
191 # Distinguish based on field name:
192 # - cellCentre, cellSize, h, x: these are Vector<2,double> fields (stored as i64 pointers)
193 # - t, dt, id: these are scalar f64 fields
194 if field_name in ['cellCentre', 'cellSize', 'h', 'x']:
195 type_map.update({field_name: 'memref<?xi64>'}) # Vector fields (pointers to vectors)
196 else:
197 type_map.update({field_name: 'memref<?xf64>'}) # Scalar fields (f64 values)
198 elif target_type == 'i64':
199 type_map.update({field_name: 'i64'})
200 elif target_type == '!llvm.ptr':
201 # For MLIR pointer types, keep them as-is (they're function pointers, not data pointers)
202 type_map.update({field_name: '!llvm.ptr'})
203 else:
204 # For other types, use the target_type directly
205 type_map.update({field_name: target_type})
206
207 def __init__(self):
208 self._parent = None
209 self._on_left = False
210 self._hoist = False
211 self._index_type = False
212
213 def parent(self, parent = None):
214 if parent is not None:
215 self._parent = parent
216 return self._parent
217
218 def on_left(self, left = None):
219 if left is not None:
220 self._on_left = left
221 return self._on_left
222
223 def hoist(self, flag = None):
224 if flag is not None:
225 self._hoist = flag
226 return self._hoist
227
228 @classmethod
229 def get_mlir_id(cls):
230 current_reg = cls.mlir_id_countmlir_id_count
232 return current_reg
233
234 @classmethod
238 @staticmethod
239 def get_mlir_type(type_string):
240 # Handle string representations by creating temporary type objects
241 if isinstance(type_string, str):
242 if type_string == "int":
243 return TInteger().print_mlir(0)
244 elif type_string == "double":
245 return TFloat().print_mlir(0)
246 elif type_string == "bool":
247 return TBoolean().print_mlir(0)
248 return None
249
250 @staticmethod
251 def generate_pointer_to_2d_element(memref_mlir, row_index, col_index, is_nested, indent_level=0):
252 indent = ' ' * indent_level * Node.spaces_per_tab
253 mlir_code = ""
254
255 if is_nested:
256 # Load inner memref from outer array, then offset within it
257 inner_memref_id = f"%{Node.get_mlir_id()}"
258 mlir_code += indent + f"{inner_memref_id} = memref.load {memref_mlir}[{row_index}] {{name = \"{memref_mlir.strip('%')}\"}} : memref<?xmemref<?xf64>>\n"
259
260 # Extract pointer as index from inner memref
261 ptr_idx_id = f"%{Node.get_mlir_id()}"
262 mlir_code += indent + f"{ptr_idx_id} = memref.extract_aligned_pointer_as_index {inner_memref_id} : memref<?xf64> -> index {{name = \"{memref_mlir.strip('%')}\"}}\n"
263
264 # Byte offset calculation (col * 8 for f64 elements)
265 element_size_bytes = f"%{Node.get_mlir_id()}"
266 mlir_code += indent + f"{element_size_bytes} = arith.constant 8 : index\n"
267
268 offset_bytes_id = f"%{Node.get_mlir_id()}"
269 mlir_code += indent + f"{offset_bytes_id} = arith.muli {col_index}, {element_size_bytes} : index\n"
270
271 # Add offset to base pointer
272 ptr_with_offset = f"%{Node.get_mlir_id()}"
273 mlir_code += indent + f"{ptr_with_offset} = arith.addi {ptr_idx_id}, {offset_bytes_id} : index\n"
274
275 # Convert to i64, then to llvm.ptr
276 ptr_i64 = f"%{Node.get_mlir_id()}"
277 mlir_code += indent + f"{ptr_i64} = arith.index_cast {ptr_with_offset} : index to i64\n"
278
279 result_ptr = f"%{Node.get_mlir_id()}"
280 mlir_code += indent + f"{result_ptr} = llvm.inttoptr {ptr_i64} : i64 to !llvm.ptr\n"
281 else:
282 # Flat 2D memref: compute (row * width + col) offset
283 col_width_id = f"%{Node.get_mlir_id()}"
284 one_const = f"%{Node.get_mlir_id()}"
285 mlir_code += indent + f"{one_const} = arith.constant 1 : index\n"
286 mlir_code += indent + f"{col_width_id} = memref.dim {memref_mlir}, {one_const} : memref<?x?xf64>\n"
287
288 # Calculate row offset (row * width)
289 row_offset = f"%{Node.get_mlir_id()}"
290 mlir_code += indent + f"{row_offset} = arith.muli {row_index}, {col_width_id} : index\n"
291
292 # Add column to row offset
293 elem_offset = f"%{Node.get_mlir_id()}"
294 mlir_code += indent + f"{elem_offset} = arith.addi {row_offset}, {col_index} : index\n"
295
296 # Convert element offset to bytes
297 element_size_bytes = f"%{Node.get_mlir_id()}"
298 mlir_code += indent + f"{element_size_bytes} = arith.constant 8 : index\n"
299
300 offset_bytes_id = f"%{Node.get_mlir_id()}"
301 mlir_code += indent + f"{offset_bytes_id} = arith.muli {elem_offset}, {element_size_bytes} : index\n"
302
303 # Extract base pointer from memref
304 base_ptr_idx = f"%{Node.get_mlir_id()}"
305 mlir_code += indent + f"{base_ptr_idx} = memref.extract_aligned_pointer_as_index {memref_mlir} : memref<?x?xf64> -> index {{name = \"{memref_mlir.strip('%')}\"}}\n"
306
307 # Add byte offset to base
308 ptr_with_offset = f"%{Node.get_mlir_id()}"
309 mlir_code += indent + f"{ptr_with_offset} = arith.addi {base_ptr_idx}, {offset_bytes_id} : index\n"
310
311 # Convert to i64, then to llvm.ptr
312 ptr_i64 = f"%{Node.get_mlir_id()}"
313 mlir_code += indent + f"{ptr_i64} = arith.index_cast {ptr_with_offset} : index to i64\n"
314
315 result_ptr = f"%{Node.get_mlir_id()}"
316 mlir_code += indent + f"{result_ptr} = llvm.inttoptr {ptr_i64} {{name = \"{memref_mlir.strip('%')}\"}} : i64 to !llvm.ptr\n"
317
318 return mlir_code, result_ptr
319
320 @classmethod
321 def create_memref_from_ptr(cls, ptr_id, target_type, indent_level=0, size_hint=None, target_var_name=None):
322 indent = ' ' * indent_level * cls.spaces_per_tab
323
324 # Generate unique IDs for all the operations
325 desc_id = f"%{cls.get_mlir_id()}"
326 desc_0_id = f"%{cls.get_mlir_id()}"
327 desc_1_id = f"%{cls.get_mlir_id()}"
328 desc_2_id = f"%{cls.get_mlir_id()}"
329 desc_3_id = f"%{cls.get_mlir_id()}"
330 desc_4_id = f"%{cls.get_mlir_id()}"
331
332 # Use target variable name if provided, otherwise generate a new one
333 if target_var_name:
334 memref_id = target_var_name
335 else:
336 memref_id = f"%{cls.get_mlir_id()}"
337
338 # Create constants we need
339 c0_i64_id = f"%{cls.get_mlir_id()}"
340 c1_i64_id = f"%{cls.get_mlir_id()}"
341
342 # Use standard LLVM memref descriptor format - this is part of the fix supplied by Nick Brown
343 # For 2D memrefs, use 2D descriptor; for 1D memrefs, use 1D descriptor
344 if target_type.startswith('memref<?x?'):
345 # 2D memref needs 2D descriptor
346 struct_type = '!llvm.struct<(ptr, ptr, i64, array<2 x i64>, array<2 x i64>)>'
347 num_dimensions = 2
348 else:
349 # 1D memref needs 1D descriptor
350 struct_type = '!llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)>'
351 num_dimensions = 1
352
353 # Determine size based on target type or hint
354 if target_type.startswith('memref<?xmemref') or size_hint == 1:
355 size_val = "1"
356 elif target_type.startswith('memref<?xi64>') and size_hint == 2:
357 size_val = "2"
358 elif target_type.startswith('memref<?xi64>'):
359 size_val = "2" # Default for vector types
360 else:
361 size_val = "1" # Default for scalar-like types
362
363 mlir_str = ""
364
365 # Create the required constants
366 mlir_str += indent + f"{c0_i64_id} = arith.constant 0 : i64\n"
367 mlir_str += indent + f"{c1_i64_id} = arith.constant 1 : i64\n"
368
369 # If we need a size constant, create it
370 if size_val != "1":
371 size_id = f"%{cls.get_mlir_id()}"
372 size_i64_id = f"%{cls.get_mlir_id()}"
373 mlir_str += indent + f"{size_id} = arith.constant {size_val} : index\n"
374 mlir_str += indent + f"{size_i64_id} = arith.index_cast {size_id} : index to i64\n"
375 size_ref = size_i64_id
376 else:
377 size_ref = c1_i64_id
378
379 # Create the LLVM memref descriptor
380 mlir_str += indent + f"{desc_id} = llvm.mlir.undef : {struct_type}\n"
381 mlir_str += indent + f"{desc_0_id} = llvm.insertvalue {ptr_id}, {desc_id}[0] : {struct_type}\n"
382 mlir_str += indent + f"{desc_1_id} = llvm.insertvalue {ptr_id}, {desc_0_id}[1] : {struct_type}\n"
383 mlir_str += indent + f"{desc_2_id} = llvm.insertvalue {c0_i64_id}, {desc_1_id}[2] : {struct_type}\n"
384 mlir_str += indent + f"{desc_3_id} = llvm.insertvalue {size_ref}, {desc_2_id}[3, 0] : {struct_type}\n"
385 if num_dimensions == 2:
386 # For 2D descriptor, set [3, 1], [4, 0] and [4, 1]
387 desc_3_1_id = f"%{cls.get_mlir_id()}"
388 mlir_str += indent + f"{desc_3_1_id} = llvm.insertvalue {size_ref}, {desc_3_id}[3, 1] : {struct_type}\n"
389 mlir_str += indent + f"{desc_4_id} = llvm.insertvalue {c1_i64_id}, {desc_3_1_id}[4, 0] : {struct_type}\n"
390 desc_final_id = f"%{cls.get_mlir_id()}"
391 mlir_str += indent + f"{desc_final_id} = llvm.insertvalue {c1_i64_id}, {desc_4_id}[4, 1] : {struct_type}\n"
392 else:
393 # For 1D descriptor, just set [4, 0]
394 mlir_str += indent + f"{desc_4_id} = llvm.insertvalue {c1_i64_id}, {desc_3_id}[4, 0] : {struct_type}\n"
395 desc_final_id = desc_4_id
396 mlir_str += indent + f"{memref_id} = builtin.unrealized_conversion_cast {desc_final_id} : {struct_type} to {target_type} {{name = \"{memref_id.strip('%')}\"}}\n"
397
398 return mlir_str, memref_id
399
400 @classmethod
401 def create_memref_from_extracted_descriptor(cls, alloc_ptr_id, aligned_ptr_id, offset_id, sizes_0_id, sizes_1_id, strides_0_id, strides_1_id, target_type, struct_type, indent_level=0, target_var_name=None, is_2d=False):
402 indent = ' ' * indent_level * cls.spaces_per_tab
403
404 # Use target variable name if provided
405 if target_var_name:
406 memref_id = target_var_name
407 else:
408 memref_id = f"%{cls.get_mlir_id()}"
409
410 # Generate unique IDs for all the operations
411 desc_id = f"%{cls.get_mlir_id()}"
412 desc_0_id = f"%{cls.get_mlir_id()}"
413 desc_1_id = f"%{cls.get_mlir_id()}"
414 desc_2_id = f"%{cls.get_mlir_id()}"
415 desc_3_id = f"%{cls.get_mlir_id()}"
416 desc_4_id = f"%{cls.get_mlir_id()}"
417
418 c1_i64_id = f"%{cls.get_mlir_id()}"
419
420 mlir_str = ""
421
422 # Create constants we need
423 mlir_str += indent + f"{c1_i64_id} = arith.constant 1 : i64\n"
424
425 # Create the LLVM memref descriptor using the extracted fields
426 # We preserve the extracted pointers but hardcode sizes and strides to 1
427 mlir_str += indent + f"{desc_id} = llvm.mlir.undef : {struct_type}\n"
428 mlir_str += indent + f"{desc_0_id} = llvm.insertvalue {alloc_ptr_id}, {desc_id}[0] : {struct_type}\n"
429 mlir_str += indent + f"{desc_1_id} = llvm.insertvalue {aligned_ptr_id}, {desc_0_id}[1] : {struct_type}\n"
430 mlir_str += indent + f"{desc_2_id} = llvm.insertvalue {offset_id}, {desc_1_id}[2] : {struct_type}\n"
431 mlir_str += indent + f"{desc_3_id} = llvm.insertvalue {sizes_0_id}, {desc_2_id}[3, 0] : {struct_type}\n"
432
433 # Check if 2D or 1D
434 if is_2d:
435 # 2D descriptor - use all extracted components
436 desc_3_1_id = f"%{cls.get_mlir_id()}"
437 mlir_str += indent + f"{desc_3_1_id} = llvm.insertvalue {sizes_1_id}, {desc_3_id}[3, 1] : {struct_type}\n"
438 mlir_str += indent + f"{desc_4_id} = llvm.insertvalue {strides_0_id}, {desc_3_1_id}[4, 0] : {struct_type}\n"
439 desc_final_id = f"%{cls.get_mlir_id()}"
440 mlir_str += indent + f"{desc_final_id} = llvm.insertvalue {strides_1_id}, {desc_4_id}[4, 1] : {struct_type}\n"
441 else:
442 # 1D descriptor - use strides_0_id
443 mlir_str += indent + f"{desc_4_id} = llvm.insertvalue {strides_0_id}, {desc_3_id}[4, 0] : {struct_type}\n"
444 desc_final_id = desc_4_id
445
446 mlir_str += indent + f"{memref_id} = builtin.unrealized_conversion_cast {desc_final_id} : {struct_type} to {target_type} {{name = \"{memref_id.strip('%')}\"}}\n"
447
448 return mlir_str, memref_id
449
450 @abstractmethod
451 def print_cpp(self, indent_level = 0):
452 pass
453
454 @abstractmethod
455 def print_mlir(self, indent_level = 0):
456 pass
457
458 @abstractmethod
459 def print_omp(self, indent_level = 0):
460 pass
461
462 @abstractmethod
463 def print_sycl(self, indent_level = 0):
464 pass
465
466 @abstractmethod
467 def print_tree(self, indent_level = 0):
468 pass
469
470class Statement(Node):
471 def __init__(self):
472 super().__init__()
473
474
476 def __init__(self):
477 super().__init__()
478 self._mlir_id = None
479
480 def mlir_id(self, id = None):
481 if id is not None:
482 self._mlir_id = id
483 return self._mlir_id
484
485 def multidimensional_index(self, index_list, start_index=0, dimensions = []):
486 return self
487
488 @abstractmethod
489 def get_type(self):
490 pass
491
492
493class Type(Node):
494
495 def print_tree(self, indent_level=0):
496 return ' '
497
499 return None
500
501
502class TVoid(Type):
503 def print_cpp(self, indent_level=0):
504 return ' ' * indent_level * Node.spaces_per_tab + 'void'
505
506 def print_mlir(self, indent_level=0):
507 return ' ' * indent_level * Node.spaces_per_tab + 'void'
508
509 def print_omp(self, indent_level=0):
510 return ' ' * indent_level * Node.spaces_per_tab + 'void'
511
512 def print_sycl(self, indent_level=0):
513 return ' ' * indent_level * Node.spaces_per_tab + 'void'
514
515 def is_function(self):
516 return False
517
518
519
521 def __init__(self, id, argument_type: Type, namespace = None, is_function = False):
522 super().__init__()
523 self.id = id
524 self._type = argument_type
525 self._type.parent(self)
526 self._namespace = namespace
527 self._is_function = is_function
528 if Node.use_accelerator == True:
529 if type(self._type) is TCustom and self._type._type.find("CellData") != -1:
530 index = self._type._type.find("CellData")
531 self._type._type = self._type._type[0:index] + "CopyCellDataGPU" + self._type._type[index + 8:]
532
533 def print_cpp(self, indent_level = 0):
534 return self._type.print_cpp() + " " + self.id
535
536 def print_omp(self, indent_level = 0):
537 return self._type.print_omp() + " " + self.id
538
539 def print_sycl(self, indent_level = 0):
540 return self._type.print_sycl() + " " + self.id
541
542 def print_mlir(self, indent_level = 0):
543 # NOTE: Arguments are inserted in-line into 'func.func' op
544 return f"%{self.id} : {self._type.print_mlir(0)}"
545
546 def print_tree(self, indent_level = 0):
547 return ' ' * indent_level * Node.spaces_per_tab + f"Argument: {self.id}"
548
549
551 _stateless = False
552 _syclDataToCopy = []
553
554 def __init__(self, id, return_type: Type, template = None, namespaces = [], stateless = False, top_level = False):
555 super().__init__()
556 self.id = id
557 self._return_type = return_type
558 self._return_type.parent(self)
559 self._template = template
560 self._arguments = []
561 self._body = []
562 self._namespaces = namespaces
563 FunctionDefinition._stateless = stateless
564 self._top_level = top_level
565
566 def top_level(self, top = None):
567 if top is not None:
568 self._top_level = top
569 return self._top_level
570
571 def add_statement(self, statement: Statement):
572 if statement is not None:
573 self._body.append(statement)
574
575 def add_argument(self, argument: Argument):
576 self._arguments.append(argument)
577 if self._top_level:
578 # NOTE: For now, we assume that the top-level function is always the main entry point
579 # and set global values in Node here - 'namespace' is MLIR type here
580 Node.type_map.update({argument.id: argument._namespace})
581 if Node.type_map.get(argument._type.print_cpp()) is None:
582 Node.type_map.update({argument._type.print_cpp(): argument._namespace})
583
584 def print_declaration(self, indent_level = 0):
585 namespace_header = f"""namespace {"::".join(self._namespaces)} {{"""
586 namespace_footer = "}"
587 template_string = ""
588 if self._template is not None:
589 template_strings = [argument.print_cpp() for argument in self._template]
590 template_string = "template <" + ",".join(template_strings) + ">\n"
591
592 current_indent = indent_level + 1 # Indentation increases inside namespace
593 argument_prints = [argument.print_cpp() for argument in self._arguments]
594
595 return f"""{namespace_header}
596{template_string}{' ' * current_indent * Node.spaces_per_tab}{self._return_type.print_cpp()} {self.id}({', '.join(argument_prints)});
597{namespace_footer}
598"""
599
600 def print_cpp(self, indent_level=0):
601 namespace_header = f"""namespace {"::".join(self._namespaces)} {{"""
602 namespace_footer = "}"
603 template_string = ""
604 if self._template is not None:
605 template_strings = [argument.print_cpp() for argument in self._template]
606 template_string = "template <" + ",".join(template_strings) + ">\n"
607
608 current_indent = indent_level + 1 # Indentation increases inside namespace
609 statement_prints = [statement.print_cpp(current_indent + 1) for statement in self._body]
610 argument_prints = [argument.print_cpp() for argument in self._arguments]
611
612 return f"""{namespace_header}
613{template_string}{' ' * current_indent * Node.spaces_per_tab}{self._return_type.print_cpp()} {self.id}({', '.join(argument_prints)}) {{
614{os.linesep.join(statement_prints)}
615{' ' * current_indent * Node.spaces_per_tab}}}
616{namespace_footer}
617"""
618
619 def print_omp(self, indent_level=0):
620 namespace_header = f"""namespace {"::".join(self._namespaces)} {{"""
621 namespace_footer = "}"
622 template_string = ""
623 if self._template is not None:
624 template_strings = [argument.print_omp() for argument in self._template]
625 template_string = "template <" + ",".join(template_strings) + ">\n"
626
627 current_indent = indent_level + 1 # Indentation increases inside namespace
628 statement_prints = [statement.print_omp(current_indent + 1) for statement in self._body]
629 argument_prints = [argument.print_omp() for argument in self._arguments]
630
631 return f"""{namespace_header}
632{template_string}{' ' * current_indent * Node.spaces_per_tab}{self._return_type.print_omp()} {self.id}({', '.join(argument_prints)}) {{
633{os.linesep.join(statement_prints)}
634{' ' * current_indent * Node.spaces_per_tab}}}
635{namespace_footer}
636"""
637
638 def print_sycl(self, indent_level=0):
639 namespace_header = f"""namespace {"::".join(self._namespaces)} {{"""
640 namespace_footer = "}"
641 template_string = ""
642 if self._template is not None:
643 template_strings = [argument.print_sycl() for argument in self._template]
644 template_string = "template <" + ",".join(template_strings) + ">\n"
645
646 current_indent = indent_level + 1 # Indentation increases inside namespace
647 statement_prints = [statement.print_sycl(current_indent + 1) for statement in self._body]
648 argument_prints = [argument.print_sycl() for argument in self._arguments]
649 statement_prints.insert(0, "::sycl::queue& queue = tarch::accelerator::getSYCLQueue(targetDevice);")
650 statement_prints.insert(1, "size_t range0, range1, range2;")
651
652 data_copy_prints = []
653 for dataBlockCreation in FunctionDefinition._syclDataToCopy:
654 if len(dataBlockCreation._dataBlock._iteration_range) > 1:
655 step_size = Integer(1)
656 for d in dataBlockCreation._dataBlock._iteration_range[0:-1]:
657 step_size = step_size * (d[1] - d[0])
658 size = step_size * (dataBlockCreation._dataBlock._iteration_range[-1][1] - dataBlockCreation._dataBlock._iteration_range[-1][0])
659
660 return f"""{namespace_header}
661{template_string}{' ' * current_indent * Node.spaces_per_tab}{self._return_type.print_sycl()} {self.id}({', '.join(argument_prints)}) {{
662{os.linesep.join(statement_prints)}
663{os.linesep.join(data_copy_prints)}
664{' ' * current_indent * Node.spaces_per_tab}}}
665{namespace_footer}
666"""
667
668 def print_mlir(self, indent_level = 0):
669 indent_level += 1 # Increase indentation for MLIR
670
671 if self._top_level:
672 Node.reset_mlir_ids() # Reset the MLIR id count for the top-level function
673
674 # NOTE: We used to generate MLIRbridge.cpp using the dedicated MLIRBridgeGenerator module
675 globals_mlir = []
676
677 # Add external function declarations for bridge functions using dynamic signatures
678 # Extract function signatures directly from self._template (functors only, not constants)
679 if self._template is not None:
680 for argument in self._arguments:
681 mlir_signature = argument._namespace #Node.type_map[cpp_type_str]
682
683 # Only process functors, skip template constants like "NumberOfVolumesPerAxisInPatch"
684 if argument._is_function:
685 # Create the bridge function declaration in MLIR with _bridge suffix
686 # This matches the C++ wrapper function names (flux_bridge, etc.)
687 func_decl = f"func.func private @{argument.id}_bridge{mlir_signature}"
688 globals_mlir.append(' ' * (indent_level) * Node.spaces_per_tab + func_decl)
689
690
691 else:
692 # No template means no MLIR bridge functions are needed
693 raise ValueError(
694 "MLIR code generation requires template parameters to determine bridge function signatures. "
695 "Ensure the function has template parameters that specify the required functors."
696 )
697
698 # Add addressof/loads for globals inside the function
699 function_global_loads = []
700 if self._template is not None:
701 for argument in self._template:
702 # Generate globals for ALL template parameters (including boolean evaluation flags)
703 new_id = Node.get_mlir_id()
704 globals_mlir.append(' ' * (indent_level) * Node.spaces_per_tab +f"llvm.mlir.global external @{argument.id}() : {argument._type.print_mlir(0)}")
705 function_global_loads.append(' ' * (indent_level + 1) * Node.spaces_per_tab + f"%{new_id} = llvm.mlir.addressof @{argument.id} : !llvm.ptr")
706
707 # Get the MLIR type for the argument
708 arg_mlir_type = argument._type.print_mlir(0)
709
710 # Skip global loading for variables with empty types (be more thorough)
711 if arg_mlir_type and arg_mlir_type.strip() != '' and len(arg_mlir_type.strip()) > 0:
712 function_global_loads.append(' ' * (indent_level + 1) * Node.spaces_per_tab + f"%{argument.id} = llvm.load %{new_id} {{name = \"{argument.id.strip('%')}\"}} : !llvm.ptr -> {arg_mlir_type}")
713 else:
714 # For variables with empty types, just create a comment
715 function_global_loads.append(' ' * (indent_level + 1) * Node.spaces_per_tab + f"// Skipping load for {argument.id} (empty type: '{arg_mlir_type}')")
716 else:
717 globals_mlir = []
718
719 Node.context.push_block()
720 argument_prints = [argument.print_mlir(indent_level).strip('') for argument in self._arguments]
721 Node.context.push_block()
722
723 statement_prints = [statement.print_mlir(indent_level + 1) for statement in self._body]
724 statements = Node.context.pop_block().get_mlir()
725 statements += statement_prints
726
727 return_type = self._return_type.print_mlir(0)
728 return_type_str = f" -> ({return_type})" if return_type != 'void' else ''
729
730 return f"""
731builtin.module {{
732{os.linesep.join(globals_mlir)}
733 func.func @{self.id + "_omp" if Node.use_mlir_omp else self.id}({', '.join(argument_prints)}){return_type_str} {{
734{os.linesep.join(function_global_loads)}
735{os.linesep.join(statements)}
736func.return
737 }}
738}}
739"""
740 # Non-top-level case
741 Node.context.push_block()
742 argument_prints = [argument.print_mlir(indent_level) for argument in self._arguments]
743 Node.context.push_block()
744 statement_prints = [statement.print_mlir(indent_level + 1) for statement in self._body]
745 statements = Node.context.pop_block().get_mlir()
746 statements += statement_prints
747 return_type = self._return_type.print_mlir(indent_level)
748 return_type_str = f" -> ({return_type})" if return_type != 'void' else ''
749 return f"""
750 func.func @{self.id}({', '.join(argument_prints)}){return_type_str} {{
751{os.linesep.join(statements)}
752func.return
753 }}
754"""
755
756 def print_tree(self, indent_level = 0):
757 statement_prints = [statement.print_tree(indent_level + 1) for statement in self._body]
758 argument_prints = [argument.print_tree(indent_level + 1) for argument in self._arguments]
759 return f"""
760FunctionDefinition: {self.id}:
761{os.linesep.join(argument_prints)}
762{os.linesep.join(statement_prints)}"""
763
764 def print_definition_with_timer(self, indent_level = 0):
765 namespace_header = f"""namespace {"::".join(self._namespaces)} {{"""
766 namespace_footer = "}"
767 template_string = ""
768 function_call_string = self.id
769 if self._template is not None:
770 template_strings = [argument.print_cpp() for argument in self._template]
771 template_string = "template <" + ",".join(template_strings) + ">\n"
772 function_call_string += "<" + ",".join([template.split(' ')[1] for template in template_strings]) + ">"
773
774 arguments = copy.deepcopy(self._arguments)
775 arguments.append(Argument("measurement", TCustom("tarch::timing::Measurement&")))
776
777 current_indent = indent_level + 1 # Indentation increases inside namespace
778 argument_prints = [argument.print_cpp() for argument in arguments]
779
780 return f"""{namespace_header}
781{template_string}{' ' * current_indent * Node.spaces_per_tab}{self._return_type.print_cpp()} {self.id}({', '.join(argument_prints)}) {{
782tarch::timing::Watch watch("{"::".join(self._namespaces)}", "{self.id}", false, true);
783{function_call_string}({",".join([argument.print_cpp().split(' ')[-1] for argument in self._arguments])});
784watch.stop();
785measurement.setValue(watch.getCalendarTime());
786{' ' * current_indent * Node.spaces_per_tab}}}
787{namespace_footer}
788"""
789
790 def print_declaration_with_timer(self, indent_level = 0):
791 namespace_header = f"""namespace {"::".join(self._namespaces)} {{"""
792 namespace_footer = "}"
793 template_string = ""
794 if self._template is not None:
795 template_strings = [argument.print_cpp() for argument in self._template]
796 template_string = "template <" + ",".join(template_strings) + ">\n"
797
798 arguments = copy.deepcopy(self._arguments)
799 arguments.append(Argument("measurement", TCustom("tarch::timing::Measurement&")))
800
801 current_indent = indent_level + 1 # Indentation increases inside namespace
802 argument_prints = [argument.print_cpp() for argument in arguments]
803
804 return f"""{namespace_header}
805{template_string}{' ' * current_indent * Node.spaces_per_tab}{self._return_type.print_cpp()} {self.id}({', '.join(argument_prints)});
806{namespace_footer}
807"""
808
810 def __init__(self, statementToLog, outputStream):
811 self._statement = statementToLog
812 self._output = outputStream
813
814 def print_cpp(self, indent_level=0):
815 return f"""{self._output.print_cpp()} << {self._statement.print_cpp()};"""
816
817 def print_omp(self, indent_level=0):
818 return f"""{self._output.print_omp()} << {self._statement.print_omp()};"""
819
820 def print_sycl(self, indent_level=0):
821 return f"""{self._output.print_sycl()} << {self._statement.print_sycl()};"""
822
823 def print_mlir(self, indent_level=0):
824 pass
825
826 def print_tree(self, indent_level=0):
827 return "LogToFile"
828
829
830# Expressions
832 variables = dict()
833
834 def __init__(self, id, type = None):
835 super().__init__()
836 self.id = id
837 self.type = type
838 if self.type is None:
839 self.type = Name.variables[self.id].get_type()
840 self.type._parent = self
841
842 def multidimensional_index(self, index_list, start_index=0, dimensions = []):
843 if type(Name.variables[self.id].get_type()) is TDataBlock or type(Name.variables[self.id].get_type()) is TDiagonalMatrix:
844 return Name.variables[self.id].multidimensional_index(index_list, start_index, dimensions)
845 else:
846 return self
847
848 def index(self, i):
849 return Name.variables[self.id].index(i)
850
851 def index(self, i, j):
852 return Name.variables[self.id].index(i, j)
853
854 def __add__(self, rhs):
855 return BinaryOperation("+", self, rhs)
856
857 def __sub__(self, rhs):
858 return BinaryOperation("-", self, rhs)
859
860 def __mul__(self, rhs):
861 return BinaryOperation("*", self, rhs)
862
863 def __div__(self, rhs):
864 return BinaryOperation("/", self, rhs)
865
866 def __neg__(self):
867 return UnaryOperation("-", self)
868
869 def __gt__(self, rhs):
870 if type(rhs) is Name:
871 return (Name.variables[self.id] > Name.variables[rhs.id])
872 return (Name.variables[self.id] > rhs)
873
874 def __eq__(self, rhs):
875 if type(rhs) is Name:
876 if rhs.id in Name.variables and self.id in Name.variables:
877 return (self._value == Name.variables[rhs.id])
878 else:
879 return False
880 else:
881 if self.id in Name.variables:
882 return (Name.variables[self.id] == rhs)
883 else:
884 return False
885
886 def get_type(self):
887 return self.type
888
889 def print_cpp(self, indent_level=0):
890 return ' ' * indent_level * Node.spaces_per_tab + self.id
891
892 def print_omp(self, indent_level=0):
893 return ' ' * indent_level * Node.spaces_per_tab + self.id
894
895 def print_sycl(self, indent_level=0):
896 return ' ' * indent_level * Node.spaces_per_tab + self.id
897
898 def print_mlir(self, indent_level=0):
899 # NOTE: Names are returned and used in-linei
900 return "%" + self.id
901
902 def print_tree(self, indent_level=0):
903 return ' ' * indent_level * Node.spaces_per_tab + "Name:" + self.id
904
905class Index:
906 pass
907
908class Integer(Expression):
909 def __init__(self, value, string = None):
910 super().__init__()
911 self._value = int(value)
912 self._string = string
913
914 def __add__(self, rhs):
915 if self._value == 0:
916 return rhs
917 if type(rhs) is Integer:
918 return Integer(self._value + rhs._value)
919 return BinaryOperation("+", self, rhs)
920
921 def __sub__(self, rhs):
922 if self._value == 0:
923 if type(rhs) is Integer:
924 return Integer(-rhs._value)
925 if type(rhs) is UnaryOperation and rhs._operation == "-":
926 return rhs._operand
927 return -rhs
928 if type(rhs) is Integer:
929 return Integer(self._value - rhs._value)
930 return BinaryOperation("-", self, rhs)
931
932 def __mul__(self, rhs):
933 return BinaryOperation("*", self, rhs)
934
935 def __div__(self, rhs):
936 return BinaryOperation("/", self, rhs)
937
938 def __neg__(self):
939 if self._value == 0:
940 return self
941 else:
942 return UnaryOperation("-", self)
943
944 def __lt__(self, rhs):
945 if type(rhs) is Name:
946 if rhs.id in Name.variables:
947 return (self._value < Name.variables[rhs.id])
948 else:
949 return False
950 return (self._value < rhs)
951
952 def __gt__(self, rhs):
953 if type(rhs) is Name:
954 if rhs.id in Name.variables:
955 return (self._value > Name.variables[rhs.id])
956 else:
957 return False
958 return (self._value > rhs)
959
960 def __eq__(self, rhs):
961 if type(rhs) is Integer:
962 return (self._value == rhs._value)
963
964 def __int__(self):
965 return self._value
966
967 def get_type(self):
968 # Check if this Integer references a struct field in struct_map
969 if self._string is not None and '.' in self._string:
970 struct, field = self._string.split('.')
971 if struct in Node.struct_map:
972 struct_list = Node.struct_map[struct]
973 # Find the field and get its type
974 for item in struct_list:
975 if item[0] == field:
976 # Extract the target type using '|||' separator (struct_map format: "target_type|||outer_struct_type")
977 mlir_type = item[1]
978 target_type = mlir_type.split('|||')[0] if '|||' in mlir_type else mlir_type
979 # Create a type object based on the target type
980 if target_type == 'i64':
981 # Return a wrapper that will generate i64
982 return TLong()
983 elif target_type == 'i32':
984 return TInteger()
985 elif target_type == 'f64':
986 return TFloat()
987 # Default: return regular i32 TInteger
988 return TInteger()
989
990 def print_cpp(self, indent_level=0):
991 if self._string is None:
992 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
993 else:
994 return ' ' * indent_level * Node.spaces_per_tab + str(self._string)
995
996 def print_omp(self, indent_level=0):
997 if self._string is None:
998 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
999 else:
1000 return ' ' * indent_level * Node.spaces_per_tab + str(self._string)
1001
1002 def print_sycl(self, indent_level=0):
1003 if self._string is None:
1004 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1005 else:
1006 return ' ' * indent_level * Node.spaces_per_tab + str(self._string)
1007
1008 def print_mlir(self, indent_level=0):
1009 indent = ' ' * indent_level * Node.spaces_per_tab
1010
1011 if self._string is None:
1012 value = str(self._value)
1013 if self.hoist():
1014 self.mlir_id(Node.get_mlir_id())
1015 mlir_str = "".join(Node.context.get_block().pop_mlir())
1016 mlir_str += indent + f"%{self.mlir_id()} = arith.constant {self._value} : {self.get_type().print_mlir(0)}\n"
1017 Node.context.push_block()
1018 Node.context.get_block().mlir_append(mlir_str)
1019 id = f"%{self.mlir_id()}"
1020 return id
1021 return value
1022 else:
1023 # NOTE: We use Strings for the CellData structs (patchData).
1024 # So, we check the name here and we generate the MLIR to
1025 # extract the struct from LLVM pointer
1026 # TODO: We need to raise this to where we make the assignment
1027 match self._string:
1028 case x if '.' in x:
1029 struct, field = x.split('.')
1030 if struct in Node.struct_map:
1031 struct_list = Node.struct_map[struct]
1032 idx = [item[0] for item in struct_list].index(field)
1033 mlir_type = struct_list[idx][1]
1034 # Extract just the struct type (everything after the separator)
1035 mlir_type_struct = mlir_type.split('|||')[1] if '|||' in mlir_type else mlir_type
1036 # getelementptr returns !llvm.ptr and takes struct type as parameter
1037 mlir_str = f"llvm.getelementptr %{struct}[0, {idx}] : (!llvm.ptr) -> !llvm.ptr, {mlir_type_struct}"
1038 return mlir_str
1039 return self._string
1040
1041 def print_tree(self, indent_level=0):
1042 if self._string is None:
1043 return ' ' * indent_level * Node.spaces_per_tab + "Integer: " + str(self._value)
1044 else:
1045 return ' ' * indent_level * Node.spaces_per_tab + "Integer: " + str(self._string)
1046
1047
1049 def __init__(self, value):
1050 super().__init__()
1051 self._value = value
1052
1053 def __str__(self):
1054 return str(self._value)
1055
1056 def get_type(self):
1057 return TBoolean()
1058
1059 def print_cpp(self, indent_level=0):
1060 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1061
1062 def print_omp(self, indent_level=0):
1063 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1064
1065 def print_sycl(self, indent_level=0):
1066 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1067
1068 def print_mlir(self, indent_level=0):
1069 # NOTE: Literals are returned and used in-line, no hoisting required
1070 return str(self._value)
1071
1072 def print_tree(self, indent_level=0):
1073 return ' ' * indent_level * Node.spaces_per_tab + "Boolean: " + str(self._value)
1074
1076 def __init__(self, value):
1077 super().__init__()
1078 self._value = value
1079
1080 def __str__(self):
1081 return self._value
1082
1083 def get_type(self):
1084 return TString()
1085
1086 def print_cpp(self, indent_level=0):
1087 return ' ' * indent_level * Node.spaces_per_tab + self._value
1088
1089 def print_omp(self, indent_level=0):
1090 return ' ' * indent_level * Node.spaces_per_tab + self._value
1091
1092 def print_sycl(self, indent_level=0):
1093 return ' ' * indent_level * Node.spaces_per_tab + self._value
1094
1095 def print_mlir(self, indent_level=0):
1096 indent = ' ' * indent_level * Node.spaces_per_tab
1097
1098 match self._value:
1099 case x if '.' in x:
1100 struct, field = x.split('.')
1101 if struct in Node.struct_map:
1102 struct_list = Node.struct_map[struct]
1103 idx = [item[0] for item in struct_list].index(field)
1104 mlir_type_target, mlir_type_struct = struct_list[idx][1].split('|||')
1105
1106 # Generate the MLIR operations
1107 struct_val_id = f"%{Node.get_mlir_id()}"
1108
1109 # TODO: Special case: maxEigenvalue is also a function parameter, so we have to hack it here
1110 if field == "maxEigenvalue":
1111 field_id = f"%{field}s"
1112 else:
1113 field_id = f"%{Node.get_mlir_id()}"
1114
1115 # Build the basic MLIR operations
1116 mlir_str = indent + f"{struct_val_id} = llvm.load %{struct} {{name = \"{struct.strip('%')}\"}}: !llvm.ptr -> {mlir_type_struct}\n"
1117
1118 # If the extracted field is a raw pointer (not a descriptor struct),
1119 # extract it directly with a _ptr suffix to distinguish from the final memref
1120 if not mlir_type_target.startswith("!llvm.struct<"):
1121 # For raw pointers (like maxEigenvalue field [11]), use _ptr naming
1122 ptr_field_id = f"%{field}{'_ptr' if field != 'maxEigenvalue' else '_ptr'}"
1123 mlir_str += indent + f"{ptr_field_id} = llvm.extractvalue {struct_val_id}[{idx}] : {mlir_type_struct}\n"
1124 field_id = ptr_field_id
1125 else:
1126 # For descriptor structs, extract normally (will be processed below)
1127 mlir_str += indent + f"{field_id} = llvm.extractvalue {struct_val_id}[{idx}] : {mlir_type_struct}\n"
1128
1129 final_id = field_id
1130
1131 # If the extracted field is a descriptor struct, convert it to appropriate memref
1132 if mlir_type_target.startswith("!llvm.struct<"):
1133 # Use getelementptr[0, field_idx] to properly access struct fields
1134 ptr_getelementptr_id = f"%{Node.get_mlir_id()}"
1135 ptr_load_id = f"%{Node.get_mlir_id()}"
1136 mlir_str += indent + f"{ptr_getelementptr_id} = llvm.getelementptr %{struct}[0, {idx}] : (!llvm.ptr) -> !llvm.ptr, {mlir_type_struct}\n"
1137 mlir_str += indent + f"{ptr_load_id} = llvm.load {ptr_getelementptr_id} {{name = \"{struct.strip('%')}\"}}: !llvm.ptr -> {mlir_type_target}\n"
1138
1139 # Extract all 5 descriptor fields
1140 alloc_ptr_id = f"%{Node.get_mlir_id()}"
1141 aligned_ptr_id = f"%{Node.get_mlir_id()}"
1142 offset_id = f"%{Node.get_mlir_id()}"
1143 sizes_0_id = f"%{Node.get_mlir_id()}"
1144 strides_0_id = f"%{Node.get_mlir_id()}"
1145
1146 mlir_str += indent + f"{alloc_ptr_id} = llvm.extractvalue {ptr_load_id}[0] : {mlir_type_target}\n"
1147 mlir_str += indent + f"{aligned_ptr_id} = llvm.extractvalue {ptr_load_id}[1] : {mlir_type_target}\n"
1148 mlir_str += indent + f"{offset_id} = llvm.extractvalue {ptr_load_id}[2] : {mlir_type_target}\n"
1149 mlir_str += indent + f"{sizes_0_id} = llvm.extractvalue {ptr_load_id}[3, 0] : {mlir_type_target}\n"
1150 mlir_str += indent + f"{strides_0_id} = llvm.extractvalue {ptr_load_id}[4, 0] : {mlir_type_target}\n"
1151
1152 # Determine target memref type based on descriptor dimensions
1153 is_2d = "array<2 x i64>" in mlir_type_target
1154
1155 # For 2D descriptors, also extract sizes[1] and strides[1]
1156 sizes_1_id = None
1157 strides_1_id = None
1158
1159 if is_2d:
1160 sizes_1_id = f"%{Node.get_mlir_id()}"
1161 strides_1_id = f"%{Node.get_mlir_id()}"
1162 mlir_str += indent + f"{sizes_1_id} = llvm.extractvalue {ptr_load_id}[3, 1] : {mlir_type_target}\n"
1163 mlir_str += indent + f"{strides_1_id} = llvm.extractvalue {ptr_load_id}[4, 1] : {mlir_type_target}\n"
1164
1165 # Use type_map to get the correct memref type for this field
1166 # For fields like cellCentre, cellSize, t, dt, we need to use their correct types from type_map
1167 if field in Node.type_map:
1168 type_entry = Node.type_map[field]
1169 # Handle both Type objects and strings
1170 if isinstance(type_entry, str):
1171 target_memref_type = type_entry
1172 else:
1173 # Type object - call print_mlir()
1174 target_memref_type = type_entry.print_mlir(0).strip()
1175 else:
1176 # Fallback to descriptor-based inference
1177 if is_2d:
1178 # 2D descriptor
1179 target_memref_type = "memref<?x?xf64>"
1180 else:
1181 # 1D descriptor (assume array<1 x i64>)
1182 target_memref_type = "memref<?xf64>"
1183
1184 # Create memref descriptor from the extracted fields using the new method
1185 conversion_mlir, memref_id = Node.create_memref_from_extracted_descriptor(
1186 alloc_ptr_id,
1187 aligned_ptr_id,
1188 offset_id,
1189 sizes_0_id,
1190 sizes_1_id,
1191 strides_0_id,
1192 strides_1_id,
1193 target_memref_type,
1194 mlir_type_target,
1195 indent_level,
1196 target_var_name=Node.target_variable_name,
1197 is_2d=is_2d)
1198 mlir_str += conversion_mlir
1199 final_id = memref_id
1200 elif 'memref' in mlir_type_target or mlir_type_target == '!llvm.ptr':
1201 # For explicit memref types or raw pointers (like maxEigenvalue),
1202 # use create_memref_from_ptr with the extracted pointer (_ptr version)
1203 clean_target_type = mlir_type_target.strip('()')
1204 # Use the _ptr field_id for raw pointer types
1205 conversion_mlir, memref_id = Node.create_memref_from_ptr(
1206 field_id, # This is now %maxEigenvalues_ptr for raw pointers
1207 clean_target_type,
1208 indent_level,
1209 target_var_name=Node.target_variable_name)
1210 mlir_str += conversion_mlir
1211 final_id = memref_id
1212
1213 # Always append to context and return variable ID
1214 Node.context.get_block().mlir_append(mlir_str)
1215 return final_id
1216 return self._value
1217
1218 def print_tree(self, indent_level=0):
1219 return ' ' * indent_level * Node.spaces_per_tab + "String: " + self._value
1220
1221
1223 def __init__(self, value, string = None, reference = False):
1224 super().__init__()
1225 self._value = value
1226 self._string = string
1227 self._reference = reference
1228
1229
1230 def __float__(self):
1231 return self._value
1232
1233 def get_type(self):
1234 return TFloat(self._reference)
1235
1236 def print_cpp(self, indent_level=0):
1237 if self._string is None:
1238 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1239 else:
1240 return ' ' * indent_level * Node.spaces_per_tab + str(self._string)
1241
1242 def print_omp(self, indent_level=0):
1243 if self._string is None:
1244 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1245 else:
1246 return ' ' * indent_level * Node.spaces_per_tab + str(self._string)
1247
1248 def print_sycl(self, indent_level=0):
1249 if self._string is None:
1250 return ' ' * indent_level * Node.spaces_per_tab + str(self._value)
1251 else:
1252 return ' ' * indent_level * Node.spaces_per_tab + str(self._string)
1253
1254 def print_mlir(self, indent_level=0):
1255 indent = ' ' * indent_level * Node.spaces_per_tab
1256
1257 if self._string is None:
1258 value = str(self._value)
1259 if self.hoist():
1260 self.mlir_id(Node.get_mlir_id())
1261 mlir_str = "".join(Node.context.get_block().pop_mlir())
1262 mlir_str += indent + f"%{self.mlir_id()} = arith.constant {self._value} : {self.get_type().print_mlir(0)}\n"
1263 Node.context.push_block()
1264 Node.context.get_block().mlir_append(mlir_str)
1265 id = f"%{self.mlir_id()}"
1266 return id
1267 return value
1268 else:
1269 return str(self._string)
1270
1271 def print_tree(self, indent_level=0):
1272 return ' ' * indent_level * Node.spaces_per_tab + "Float: " + str(self._value)
1273
1274
1276 def __init__(self, iteration_range, internal, requires_memory_allocation, id = None, underlying_type = String("double")):
1277 super().__init__()
1278 self._iteration_range = iteration_range
1279 self._internal = internal
1280 self.requires_memory_allocation = requires_memory_allocation
1281 self._underlying_type = underlying_type
1282 self.id = id
1283 self._offset = []
1285 for [x, y] in self._iteration_range:
1286 self._offset.append(Integer(0))
1287 self._memory_range.append([Integer(0), y - x])
1288
1289 def offset(self, offset):
1290 output = DataBlock(copy.deepcopy(self._iteration_range), copy.deepcopy(self._internal), True)
1291 output.id = self.id
1292
1293 for i in range(len(offset)):
1294 if type(offset[i]) is list:
1295 if offset[i][0] is None:
1296 offset[i][0] = self._iteration_range[i][0]
1297 if offset[i][1] is None:
1298 offset[i][1] = self._iteration_range[i][1]
1299
1300 output._offset[i] = offset[i][0] - self._iteration_range[i][0]
1301 output._iteration_range[i][1] = offset[i][1]
1302 output._iteration_range[i][0] = offset[i][0]
1303 else:
1304 raise Exception("Invalid index")
1305
1306 for i in range(len(output._offset), len(output._iteration_range)):
1307 output._offset.append(Integer(0))
1308
1309 return output
1310
1311 def linearise_index(self, indices, dimensions, offset_start_index = 0):
1312 factors = [Integer(1)]
1313 for i in range(0, len(dimensions) - 1):
1314 factors.append(factors[-1] * (self._memory_range[i][1]))
1315 if Node.use_accelerator == False and type(self) is not FaceDataBlock:
1316 index_list = indices[0:-1]
1317 else:
1318 index_list = indices
1319
1320 if type(dimensions[-len(index_list)][1] - dimensions[-len(index_list)][0]) is Integer and (dimensions[-len(index_list)][1] - dimensions[-len(index_list)][0])._value == 1:
1321 index = self._offset[offset_start_index]
1322 else:
1323 if type(self._offset[offset_start_index + 0].get_type()) is TDataBlock:
1324 index = index_list[0] + self._offset[offset_start_index + 0].multidimensional_index(index_list, offset_start_index)
1325 else:
1326 index = index_list[0] + self._offset[offset_start_index + 0]
1327
1328 index = factors[-len(index_list)] * index
1329 for i in range(1, len(index_list)):
1330 if type(self._offset[offset_start_index + i].get_type()) is TDataBlock:
1331 temp = index_list[i] + self._offset[offset_start_index + i].multidimensional_index(indices, offset_start_index)
1332 else:
1333 temp = index_list[i] + self._offset[offset_start_index + i]
1334 index = index + factors[-len(index_list) + i] * temp
1335 return index
1336
1337 def multidimensional_index(self, index_list, start_index = 0, dimensions = []):
1338 if len(self._iteration_range) == 1:
1339 return Subscript(self, index_list[-1])
1340 else:
1341 if Node.use_accelerator == False:
1342 return Subscript(Subscript(self, index_list[-1]), self.linearise_index(index_list, self._iteration_range[0:-1], start_index))
1343 else:
1344 return Subscript(self, self.linearise_index(index_list, self._iteration_range, start_index))
1345
1346 def get_type(self):
1347 db_type = TDataBlock(len(self._iteration_range), self._underlying_type)
1348 return db_type
1349
1350 def print_cpp(self, indent_level=0):
1351 if self.id is None:
1352 return ' ' * indent_level * Node.spaces_per_tab + self._internal.print_cpp()
1353 else:
1354 return ' ' * indent_level * Node.spaces_per_tab + self.id
1355
1356 def print_omp(self, indent_level=0):
1357 if self.id is None:
1358 return ' ' * indent_level * Node.spaces_per_tab + self._internal.print_omp()
1359 else:
1360 return ' ' * indent_level * Node.spaces_per_tab + self.id
1361
1362 def print_sycl(self, indent_level=0):
1363 if self.id is None:
1364 return ' ' * indent_level * Node.spaces_per_tab + self._internal.print_sycl()
1365 else:
1366 return ' ' * indent_level * Node.spaces_per_tab + self.id
1367
1368
1369 def print_mlir(self, indent_level=0):
1370 if self.id is None:
1371 return self._internal.print_mlir(indent_level)
1372 else:
1373 return "%" + self.id
1374
1375 def print_tree(self, indent_level=0):
1376 if self.id is None:
1377 return ' ' * indent_level * Node.spaces_per_tab + f"""DataBlock:
1378{self._internal.print_tree(indent_level + 1)}"""
1379 else:
1380 return ' ' * indent_level * Node.spaces_per_tab + "DataBlock: " + self.id
1381
1382"""
1383FaceDataBlock distinguishes itself from normal DataBlocks by the fact that its internal array is always 1d.
1384"""
1386 def __init__(self, iteration_range, internal, requires_memory_allocation, id = None, underlying_type = String("double")):
1387 super().__init__(iteration_range, internal, requires_memory_allocation, id, underlying_type)
1388
1389 def offset(self, offset):
1390 output = FaceDataBlock(copy.deepcopy(self._iteration_range_iteration_range), copy.deepcopy(self._internal), True)
1391 output.id = self.id
1392
1393 for i in range(len(offset)):
1394 if type(offset[i]) is list:
1395 if offset[i][0] is None:
1396 offset[i][0] = self._iteration_range_iteration_range[i][0]
1397 if offset[i][1] is None:
1398 offset[i][1] = self._iteration_range_iteration_range[i][1]
1399
1400 output._offset[i] = offset[i][0] - self._iteration_range_iteration_range[i][0]
1401 output._iteration_range[i][1] = offset[i][1]
1402 output._iteration_range[i][0] = offset[i][0]
1403 else:
1404 raise Exception("Invalid index")
1405
1406 for i in range(len(output._offset), len(output._iteration_range)):
1407 output._offset.append(Integer(0))
1408
1409 return output
1410
1411 def multidimensional_index(self, index_list, start_index = 0, dimensions = []):
1412 return Subscript(self, self.linearise_index(index_list, self._iteration_range_iteration_range, start_index))
1413
1414
1416 def __init__(self, dataBlock, filename):
1417 self._filename = filename
1418
1419 self._dataBlock = dataBlock
1420
1421 loops = []
1422 index = []
1423
1424 loops.append(For([Integer(0), self._dataBlock._iteration_range[-1][1], - self._dataBlock._iteration_range[-1][0]]))
1425 for i in range(len(self._dataBlock._memory_range) - 2, -1, -1):
1426 loops.append(For([Integer(0), self._dataBlock._iteration_range[i][1] - self._dataBlock._iteration_range[i][0]]))
1427
1428 for i in range(len(self._dataBlock._memory_range) - 1, -1, -1):
1429 index.append(loops[i].get_iteration_variable())
1430
1431 loops[-1].add_statement(LogToFile(self._dataBlock.multidimensional_index(index), String("log")))
1432 loops[-1].add_statement(LogToFile(String("\" \""), String("log")))
1433 for i in range(0, len(loops) - 1):
1434 loops[i].add_statement(loops[i + 1])
1435
1436 loops[-2].add_statement(LogToFile(String("\", \""), String("log")))
1437 for i in range(0, len(self._dataBlock._memory_range) - 2):
1438 loops[i].add_statement(LogToFile(String("\"\\n\""), String("log")))
1439
1440 for loop in loops[::-1]:
1441 loop.close_scope()
1442 self._loop = loops[0]
1443
1444 def print_cpp(self, indent_level=0):
1445 return f"""log.open("{self._filename.print_cpp()}");
1446{self._loop.print_cpp()}
1447log.close();
1448"""
1449
1450 def print_omp(self, indent_level=0):
1451 return ""
1452
1453 def print_sycl(self, indent_level=0):
1454 return ""
1455
1456 def print_mlir(self, indent_level=0):
1457 return ""
1458
1459 def print_tree(self, indent_level=0):
1460 return ""
1461
1463 def __init__(self, value: Expression, index: Expression):
1464 super().__init__()
1465 self._value = value
1466 self._index = index
1467
1468 def __neg__(self):
1469 return UnaryOperation("-", self)
1470
1472 return isinstance(self._value, Subscript)
1473
1474 def get_type(self):
1475 return self._value.get_type()
1476
1477 def print_cpp(self, indent_level=0):
1478 return ' ' * indent_level * Node.spaces_per_tab + f"{self._value.print_cpp()}[{self._index.print_cpp()}]"
1479
1480 def print_omp(self, indent_level=0):
1481 return ' ' * indent_level * Node.spaces_per_tab + f"{self._value.print_omp()}[{self._index.print_omp()}]"
1482
1483 def print_sycl(self, indent_level=0):
1484 return ' ' * indent_level * Node.spaces_per_tab + f"{self._value.print_sycl()}[{self._index.print_sycl()}]"
1485
1486 def print_mlir(self, indent_level=0):
1487 indent = ' ' * indent_level * Node.spaces_per_tab
1488 dims = 1
1489 tmp = self._value
1490 while isinstance(tmp, Subscript):
1491 dims += 1
1492 tmp = tmp._value
1493
1494 self._dimensions = dims
1495 value_type = self._value.get_type()
1496 type_str = value_type.print_cpp().strip()
1497
1498 mlir_type = Node.type_map.get(type_str)
1499 if mlir_type is None:
1500 # Fallback: try to get the MLIR type from the type object itself, or use a default
1501 mlir_type = '' + value_type.print_mlir(0)
1502
1503 mlir_str = "".join(Node.context.get_block().pop_mlir())
1504
1505 self._index.hoist(True)
1506 index = self._index.print_mlir(indent_level)
1507 mlir_str += "".join(Node.context.get_block().pop_mlir())
1508
1509 if not getattr(self._index, '_index_type', False):
1510 new_index = f"%{Node.get_mlir_id()}"
1511 # Determine the actual type of the index expression
1512 index_expr_type = self._index.get_type()
1513 mlir_type_cast = index_expr_type.print_mlir(0).strip()
1514 mlir_str += indent + new_index + f" = arith.index_cast {index.strip(' ')}: {mlir_type_cast} to index\n"
1515 self._new_index = new_index
1516 else:
1517 self._new_index = index
1518
1519 # NOTE: special handling: Strings might represent struct field access
1520 if isinstance(self._value, String):
1521 # When indexing a String field (like patchData.input[i]), we need to handle the index
1522 # Get the field type to determine if it's 2D or 1D
1523 base_mlir = self._value.print_mlir(indent_level)
1524 base_type = self._value.get_type()
1525
1526 if isinstance(base_type, TDataBlock):
1527 memref_type = base_type.print_mlir(0).strip()
1528
1529 # Check if it's a 2D flat or 1D nested memref
1530 if "x?x" in memref_type:
1531 # 2D flat memref - but we only have one index, which means we need a second one
1532 # This shouldn't happen for single-level subscripts on 2D arrays
1533 result_id = f"%{Node.get_mlir_id()}"
1534 mlir_str += indent + f"{result_id} = memref.load {base_mlir}[%0, {self._new_index}] {{name = \"{base_mlir.strip('%')}\"}} : {memref_type}\n"
1535 Node.context.push_block()
1536 Node.context.get_block().mlir_append(mlir_str)
1537 return result_id
1538 else:
1539 # 1D memref - can be nested (memref<?xmemref<?xf64>>) or plain (memref<?xf64>)
1540 result_id = f"%{Node.get_mlir_id()}"
1541 mlir_str += indent + f"{result_id} = memref.load {base_mlir}[{self._new_index}] {{name = \"{base_mlir.strip('%')}\"}} : {memref_type}\n"
1542 Node.context.push_block()
1543 Node.context.get_block().mlir_append(mlir_str)
1544 return result_id
1545 else:
1546 # Not a memref type, just return the base
1547 return base_mlir
1548
1549 Node.context.push_block()
1550 Node.context.get_block().mlir_append(mlir_str)
1551
1552 self._value.hoist(True)
1553 base_value = self._value.print_mlir(indent_level)
1554
1555 # Handle nested subscripts (like source[i][j]) by generating proper load operations
1556 if self.is_nested_subscript():
1557 result_id = f"%{Node.get_mlir_id()}"
1558
1559 base_expr = tmp # The base expression without subscripts
1560 outer_base = base_expr.print_mlir(indent_level) # The base array
1561
1562 # Get the actual type of the base expression - check mlir_memref_types FIRST
1563 if outer_base in Node.mlir_memref_types:
1564 actual_memref_type = Node.mlir_memref_types[outer_base]
1565 else:
1566 # Fallback to computing from type
1567 base_expr_type = base_expr.get_type()
1568 if isinstance(base_expr_type, TDataBlock):
1569 actual_memref_type = base_expr_type.print_mlir(0).strip()
1570 else:
1571 actual_memref_type = "memref<?x?xf64>" # Fallback
1572
1573 # Check if base expression has an explicit type in type_map (nested memref override)
1574 base_name = None
1575 if isinstance(base_expr, Name):
1576 base_name = base_expr.id
1577 elif hasattr(base_expr, 'id'):
1578 # DataBlock or similar object with id attribute
1579 base_name = base_expr.id
1580
1581 if base_name and base_name in Node.type_map and _is_nested_memref(Node.type_map[base_name]):
1582 actual_memref_type = Node.type_map[base_name]
1583
1584 # Collect both indices from the nested structure
1585 indices = []
1586 current = self
1587 while isinstance(current, Subscript):
1588 indices.insert(0, current._new_index)
1589 current = current._value
1590
1591 # Generate load based on memref type structure
1592 indices_str = ", ".join(indices)
1593 mlir_str = "".join(Node.context.get_block().pop_mlir())
1594
1595 # Check if it's a nested memref (memref<?xmemref<...>>) vs flat multi-D (memref<?x?x...>)
1596 # Handle both Type objects and strings
1597 if isinstance(actual_memref_type, str):
1598 is_nested = actual_memref_type.count("memref<") > 1
1599 actual_memref_type_str = actual_memref_type
1600 else:
1601 # Type object (e.g., TNestedMemref)
1602 is_nested = actual_memref_type.is_nested_memref()
1603 actual_memref_type_str = actual_memref_type.print_mlir(0).strip()
1604
1605 if is_nested and len(indices) >= 2:
1606 # This is a nested memref (memref<?xmemref<?xf64>>)
1607 # Need to load in two steps: first get the inner memref, then load from it
1608 inner_memref_id = f"%{Node.get_mlir_id()}"
1609 # Extract the inner memref type from the outer type
1610 if actual_memref_type_str.startswith("memref<?x") and actual_memref_type_str.endswith(">>"):
1611 inner_type = actual_memref_type_str[9:-1] # Remove "memref<?x" and final ">"
1612 else:
1613 inner_type = "memref<?xf64>" # Fallback
1614
1615 mlir_str += indent + f"{inner_memref_id} = memref.load {outer_base}[{indices[0]}] {{name = \"{outer_base.strip('%')}\"}} : {actual_memref_type_str}\n"
1616 # Now load from the inner memref with remaining indices
1617 remaining_indices = ", ".join(indices[1:])
1618 mlir_str += indent + f"{result_id} = memref.load {inner_memref_id}[{remaining_indices}] {{name = \"{outer_base.strip('%')}\"}} : {inner_type}\n"
1619 else:
1620 # This is a flat multi-D memref (memref<?x?xf64>, memref<?x?x?xf64>, etc.)
1621 mlir_str += indent + f"{result_id} = memref.load {outer_base}[{indices_str}] {{name = \"{outer_base.strip('%')}\"}} : {actual_memref_type_str}\n"
1622
1623 Node.context.push_block()
1624 Node.context.get_block().mlir_append(mlir_str)
1625
1626 return result_id
1627
1628 # Default: just return the base value (may be a memref, pointer, or value)
1629 return base_value
1630
1631 def print_tree(self, indent_level=0):
1632 return f"""Subscript:
1633{self._value.print_tree(indent_level + 1)}
1634{self._index.print_tree(indent_level + 1)}"""
1635
1636
1638 def __init__(self, expression: Expression):
1639 self._expression = expression
1640
1641 def get_type(self):
1642 return self._expression.get_type()
1643
1644 def print_cpp(self, indent_level=0):
1645 return ' ' * indent_level * Node.spaces_per_tab + "&" + self._expression.print_cpp()
1646
1647 def print_omp(self, indent_level=0):
1648 return ' ' * indent_level * Node.spaces_per_tab + "&" + self._expression.print_omp()
1649
1650 def print_sycl(self, indent_level=0):
1651 return ' ' * indent_level * Node.spaces_per_tab + "&" + self._expression.print_sycl()
1652
1653 def print_mlir(self, indent_level=0):
1654 result_id = None
1655 indent = ' ' * indent_level * Node.spaces_per_tab
1656 mlir_str = "".join(Node.context.get_block().pop_mlir())
1657
1658 self._expression.hoist(True)
1659
1660 # Handle references to subscripted expressions (array elements)
1661 if isinstance(self._expression, Subscript):
1662 base_expr = self._expression._value
1663 index_expr = self._expression._index
1664
1665 base_expr.hoist(True)
1666 base_mlir = base_expr.print_mlir(indent_level)
1667
1668 mlir_str += "".join(Node.context.get_block().pop_mlir())
1669
1670 if not hasattr(self._expression, '_new_index'):
1671 # If _new_index doesn't exist, process index and generate it now
1672 index_expr.hoist(True)
1673 index_mlir = index_expr.print_mlir(indent_level)
1674 mlir_str += "".join(Node.context.get_block().pop_mlir())
1675
1676 # Create new index with proper type cast
1677 new_index = f"%{Node.get_mlir_id()}"
1678 mlir_type = ('index' if hasattr(index_expr, '_index_type') and index_expr._index_type else 'i32')
1679 mlir_str += indent + new_index + f" = arith.index_cast {index_mlir.strip(' ')}: {mlir_type} to index\n"
1680 self._expression._new_index = new_index
1681 else:
1682 new_index = self._expression._new_index
1683
1684 # Determine dimensions and type of the memref
1685 base_type = base_expr.get_type()
1686 if isinstance(base_type, TDataBlock):
1687 result_id = f"%{Node.get_mlir_id()}"
1688
1689 # For nested memrefs (like double**), handle the inner memref properly
1690 if isinstance(base_expr, Subscript):
1691 # base_expr is Subscript[DataBlock, i]
1692 # We need to check if the base_expr itself is a nested subscript or just single-level
1693 if isinstance(base_expr._value, Subscript):
1694 result_id = base_mlir
1695 else:
1696 # Check if this variable has an explicit nested type in `type_map`
1697 base_mlir_name = base_mlir.lstrip('%')
1698 is_nested_memref = False
1699 if base_mlir_name in Node.type_map and _is_nested_memref(Node.type_map[base_mlir_name]):
1700 is_nested_memref = True
1701
1702 outer_index = base_expr._new_index
1703
1704 # Use unified method from Node to generate pointer arithmetic
1705 ptr_code, result_id = Node.generate_pointer_to_2d_element(
1706 base_mlir, outer_index, new_index, is_nested_memref, indent_level
1707 )
1708
1709 mlir_str += ptr_code
1710 else:
1711 element_type = "f64" # Default to f64 - modify if needed for other types
1712 mlir_str += indent + f"{result_id} = memref.subview {base_mlir}[{new_index}][1][1] : memref<?x{element_type}> to memref<?x{element_type}>\n"
1713
1714 # Default behavior for non-subscript references
1715 Node.context.push_block()
1716 Node.context.get_block().mlir_append(mlir_str)
1717
1718 # Process the expression and return its ID
1719 return result_id if result_id is not None else self._expression.print_mlir(indent_level)
1720
1721
1722 def print_tree(self, indent_level=0):
1723 return ' ' * indent_level * Node.spaces_per_tab + f"""Reference:
1724{self._expression.print_tree(indent_level + 1)}"""
1725
1726
1728 def __init__(self, elements, element_type, string = None):
1729 self._elements = elements
1730 self._type = element_type
1731 self._string = string
1732
1733 def print_cpp(self, indent_level=0):
1734 if self._string is None:
1735 element_prints = [element.print_cpp() for element in self._elements]
1736 return ' ' * indent_level * Node.spaces_per_tab + f"""tarch::la::Vector<{len(self._elements)}, {self._type.print_cpp()}>{{{", ".join(element_prints)}}}"""
1737 return self._string
1738
1739 def print_omp(self, indent_level=0):
1740 element_prints = [element.print_cpp() for element in self._elements]
1741 return ' ' * indent_level * Node.spaces_per_tab + f"""tarch::la::Vector<{len(self._elements)}, {self._type.print_cpp()}>{{{", ".join(element_prints)}}}"""
1742
1743 def print_sycl(self, indent_level=0):
1744 element_prints = [element.print_cpp() for element in self._elements]
1745 return ' ' * indent_level * Node.spaces_per_tab + f"""tarch::la::Vector<{len(self._elements)}, {self._type.print_cpp()}>{{{", ".join(element_prints)}}}"""
1746
1747 def print_mlir(self, indent_level=0):
1748 pass
1749
1750 def print_tree(self, indent_level=0):
1751 return "Vector"
1752
1753 def get_type(self):
1754 if self._string is not None:
1755 return TCustom("auto")
1756 return TCustom(f"tarch::la::Vector<{len(self._elements)}, {self._type.print_cpp()}>")
1757
1758
1760 def __init__(self, width, internal, index_dimensions):
1761 self._internal = internal
1762 self._width = width
1763 self._index_dimensions = index_dimensions
1764 self._id = None
1765
1766 def multidimensional_index(self, index, start_index = 0, dimensions = []):
1767 diagonal_index = index[1]
1768 factor = Integer(1)
1769 for i in range(2, len(index) - 1):
1770 factor = factor * (dimensions[i - 1][1] - dimensions[i - 1][0])
1771 diagonal_index = diagonal_index + factor * index[i]
1772 return Subscript(self, diagonal_index)
1773
1774 def print_cpp(self, indent_level=0):
1775 if self._id is not None:
1776 return self._id.print_cpp(indent_level)
1777 else:
1778 return self._internal.print_cpp(indent_level)
1779
1780 def print_omp(self, indent_level=0):
1781 if self._id is not None:
1782 return self._id.print_cpp(indent_level)
1783 else:
1784 return self._internal.print_cpp(indent_level)
1785
1786 def print_sycl(self, indent_level=0):
1787 pass
1788
1789 def print_mlir(self, indent_level=0):
1790 pass
1791
1792 def print_tree(self, indent_level=0):
1793 return ' ' * indent_level * Node.spaces_per_tab + "DiagonalMatrix"
1794
1795 def get_type(self):
1796 return TDiagonalMatrix()
1797
1798
1800 def __init__(self, dimensions, internal, index_dimensions):
1801 self._internal = internal
1802 self._dimensions = dimensions
1803 self._index_dimensions = index_dimensions
1804 self._id = None
1805
1806 def index(self, row_index, column_index):
1807 return Subscript(self, row_index * self._dimensions[1] + column_index)
1808
1809 def print_cpp(self, indent_level=0):
1810 if self._id is not None:
1811 return self._id.print_cpp(indent_level)
1812 else:
1813 return self._internal.print_cpp(indent_level)
1814
1815 def print_omp(self, indent_level=0):
1816 if self._id is not None:
1817 return self._id.print_cpp(indent_level)
1818 else:
1819 return self._internal.print_cpp(indent_level)
1820
1821 def print_sycl(self, indent_level=0):
1822 pass
1823
1824 def print_mlir(self, indent_level=0):
1825 pass
1826
1827 def print_tree(self, indent_level=0):
1828 return ' ' * indent_level * Node.spaces_per_tab + "Matrix"
1829
1830 def get_type(self):
1831 return TMatrix()
1832
1833
1835 def __init__(self, id, matrix):
1836 self._id = String(id)
1837 if type(matrix) is Name:
1838 self._matrix = Name.variables[matrix.id]
1839 else:
1840 self._matrix = matrix
1841
1843
1844 lhs_matrix = self._matrix._internal._arguments[0]
1845 if type(lhs_matrix) is Name:
1846 lhs_matrix = Name.variables[lhs_matrix.id]
1847
1848 rhs_matrix = self._matrix._internal._arguments[1]
1849 if type(rhs_matrix) is Name:
1850 rhs_matrix = Name.variables[rhs_matrix.id]
1851
1852 self._assignment = For([Integer(0), lhs_matrix._width])
1853 internal_loop = For([Integer(0), rhs_matrix._width])
1854 index = self._assignment.get_iteration_variable() * rhs_matrix._width + internal_loop.get_iteration_variable()
1855 lhs_index = Subscript(lhs_matrix, self._assignment.get_iteration_variable())
1856 if type(lhs_matrix._internal) is Float or type(lhs_matrix._internal) is Integer:
1857 lhs_index = lhs_matrix._internal
1858 rhs_index = Subscript(rhs_matrix, internal_loop.get_iteration_variable())
1859 if type(rhs_matrix._internal) is Float or type(rhs_matrix._internal) is Integer:
1860 rhs_index = rhs_matrix._internal
1861 internal_loop.add_statement(Assignment(Subscript(self._id, index), BinaryOperation("*", lhs_index, rhs_index)))
1862 self._assignment.add_statement(internal_loop)
1863 internal_loop.close_scope()
1864 self._assignment.close_scope()
1865
1866 def print_cpp(self, indent_level=0):
1867 return f"""{self._memory_allocation.print_cpp(indent_level)}
1868{self._assignment.print_cpp(indent_level)}"""
1869
1870 def print_omp(self, indent_level=0):
1871 return f"""{self._memory_allocation.print_omp(indent_level)}
1872{self._assignment.print_omp(indent_level)}"""
1873
1874 def print_sycl(self, indent_level=0):
1875 pass
1876
1877 def print_mlir(self, indent_level=0):
1878 pass
1879
1880 def print_tree(self, indent_level=0):
1881 return ' ' * indent_level * Node.spaces_per_tab + "DiagonalKroneckerProduct"
1882
1883 def get_type(self):
1884 return TDiagonalMatrix()
1885
1886
1888 def __init__(self, id, matrix):
1889 self._id = String(id)
1890 if type(matrix) is Name:
1891 self._matrix = Name.variables[matrix.id]
1892 else:
1893 self._matrix = matrix
1894
1896
1897 self._assignment = For([Integer(0), self._matrix._width])
1898 self._assignment.add_statement(Assignment(Subscript(self._id, self._assignment.get_iteration_variable()), self._matrix._internal))
1899 self._assignment.close_scope()
1900
1901 def print_cpp(self, indent_level=0):
1902 return f"""{self._memory_allocation.print_cpp(indent_level)}
1903{self._assignment.print_cpp(indent_level)}"""
1904
1905 def print_omp(self, indent_level=0):
1906 return f"""{self._memory_allocation.print_omp(indent_level)}
1907{self._assignment.print_omp(indent_level)}"""
1908
1909 def print_sycl(self, indent_level=0):
1910 pass
1911
1912 def print_mlir(self, indent_level=0):
1913 pass
1914
1915 def print_tree(self, indent_level=0):
1916 return ' ' * indent_level * Node.spaces_per_tab + "DiagonalKroneckerProduct"
1917
1918 def get_type(self):
1919 return TDiagonalMatrix()
1920
1921
1923 def __init__(self, id, matrix):
1924 self._id = String(id)
1925 if type(matrix) is Name:
1926 self._matrix = Name.variables[matrix.id]
1927 else:
1928 self._matrix = matrix
1929
1930 self._memory_allocation = MemoryAllocation(self._id, TFloat(), [self._matrix._dimensions[0] * self._matrix._dimensions[1]])
1931
1932 self._initialisation = For([Integer(0), self._matrix._dimensions[0] * self._matrix._dimensions[1]])
1933 self._initialisation.add_statement(Assignment(Subscript(self._id, self._initialisation.get_iteration_variable()), Float(0.0)))
1934 self._initialisation.close_scope()
1935
1936 lhs_matrix = self._matrix._internal._arguments[0]
1937 if type(lhs_matrix) is Name:
1938 lhs_matrix = Name.variables[lhs_matrix.id]
1939
1940 rhs_matrix = self._matrix._internal._arguments[1]
1941 if type(rhs_matrix) is Name:
1942 rhs_matrix = Name.variables[rhs_matrix.id]
1943
1944 if type(lhs_matrix.get_type()) is TMatrix:
1945 lhs_dimensions = lhs_matrix._dimensions
1946 else:
1947 lhs_dimensions = [lhs_matrix._width, lhs_matrix._width]
1948
1949 if type(rhs_matrix.get_type()) is TMatrix:
1950 rhs_dimensions = rhs_matrix._dimensions
1951 else:
1952 rhs_dimensions = [rhs_matrix._width, rhs_matrix._width]
1953
1954 loops = []
1955 if type(lhs_matrix.get_type()) is TMatrix:
1956 loops.append(For([Integer(0), lhs_matrix._dimensions[0]]))
1957 loops.append(For([Integer(0), lhs_matrix._dimensions[1]]))
1958 lhs_index = loops[0].get_iteration_variable() * lhs_dimensions[1] + loops[1].get_iteration_variable()
1959 startIndex = loops[0].get_iteration_variable() * lhs_dimensions[1] * rhs_dimensions[1] * rhs_dimensions[0] + loops[1].get_iteration_variable() * rhs_dimensions[1]
1960 else:
1961 loops.append(For([Integer(0), lhs_matrix._width]))
1962 lhs_index = loops[0].get_iteration_variable()
1963 startIndex = loops[0].get_iteration_variable() * lhs_dimensions[1] * rhs_dimensions[1] * rhs_dimensions[0] + loops[0].get_iteration_variable() * rhs_dimensions[1]
1964
1965 if type(rhs_matrix.get_type()) is TMatrix:
1966 loops.append(For([Integer(0), rhs_matrix._dimensions[0]]))
1967 loops.append(For([Integer(0), rhs_matrix._dimensions[1]]))
1968 rhs_index = loops[-2].get_iteration_variable() * rhs_dimensions[1] + loops[-1].get_iteration_variable()
1969 index = startIndex + loops[-1].get_iteration_variable() + loops[-2].get_iteration_variable() * lhs_dimensions[1] * rhs_dimensions[1]
1970 else:
1971 loops.append(For([Integer(0), rhs_matrix._width]))
1972 rhs_index = loops[-1].get_iteration_variable()
1973 index = startIndex + loops[-1].get_iteration_variable() + loops[-1].get_iteration_variable() * lhs_dimensions[1] * rhs_dimensions[1]
1974
1975 lhs_subscript = Subscript(lhs_matrix, lhs_index)
1976 if type(lhs_matrix._internal) is Float or type(lhs_matrix._internal) is Integer:
1977 lhs_subscript = lhs_matrix._internal
1978 rhs_subscript = Subscript(rhs_matrix, rhs_index)
1979 if type(rhs_matrix._internal) is Float or type(rhs_matrix._internal) is Integer:
1980 rhs_subscript = rhs_matrix._internal
1981
1982 loops[-1].add_statement(Assignment(Subscript(self._id, index), BinaryOperation("*", lhs_subscript, rhs_subscript)))
1983
1984 for i in range(len(loops) - 1):
1985 loops[i].add_statement(loops[i + 1])
1986 loops[i].close_scope()
1987 loops[-1].close_scope()
1988 self._assignment = loops[0]
1989
1990 def print_cpp(self, indent_level=0):
1991 return f"""{self._memory_allocation.print_cpp(indent_level)}
1992{self._initialisation.print_cpp(indent_level)}
1993{self._assignment.print_cpp(indent_level)}"""
1994
1995 def print_omp(self, indent_level=0):
1996 return f"""{self._memory_allocation.print_omp(indent_level)}
1997{self._initialisation.print_omp(indent_level)}
1998{self._assignment.print_omp(indent_level)}"""
1999
2000 def print_sycl(self, indent_level=0):
2001 pass
2002
2003 def print_mlir(self, indent_level=0):
2004 pass
2005
2006 def print_tree(self, indent_level=0):
2007 return ' ' * indent_level * Node.spaces_per_tab + "KroneckerProduct"
2008
2009 def get_type(self):
2010 return TMatrix()
2011
2012
2014 def __init__(self, value: Expression, index: Expression):
2015 super().__init__()
2016 self._value = value
2017 self._index = index
2018
2019 def get_type(self):
2020 return self._value.get_type()
2021
2022 def print_cpp(self, indent_level=0):
2023 return ' ' * indent_level * Node.spaces_per_tab + f"{self._value.print_cpp()}({self._index.print_cpp()})"
2024
2025 def print_omp(self, indent_level=0):
2026 return ' ' * indent_level * Node.spaces_per_tab + f"{self._value.print_omp()}({self._index.print_omp()})"
2027
2028 def print_sycl(self, indent_level=0):
2029 return ' ' * indent_level * Node.spaces_per_tab + f"{self._value.print_sycl()}({self._index.print_sycl()})"
2030
2031 def print_mlir(self, indent_level=0):
2032 indent = ' ' * indent_level * Node.spaces_per_tab
2033 mlir_str = ""
2034
2035 if isinstance(self._value, Subscript):
2036 array_base = self._value._value
2037 array_index = self._value._index
2038 array_base_mlir = array_base.print_mlir(indent_level)
2039 array_index_mlir = array_index.print_mlir(indent_level)
2040 # Ensure array_index is of type 'index' with correct source type
2041 if not getattr(array_index, '_index_type', False):
2042 array_index_cast = f"%{Node.get_mlir_id()}"
2043 array_index_type = array_index.get_type()
2044 array_index_mlir_type = array_index_type.print_mlir(0).strip()
2045 mlir_str += indent + f"{array_index_cast} = arith.index_cast {array_index_mlir} : {array_index_mlir_type} to index\n"
2046 else:
2047 array_index_cast = array_index_mlir
2048
2049 raw_ptr = f"%{Node.get_mlir_id()}"
2050 mlir_str += indent + f"{raw_ptr} = memref.load {array_base_mlir}[{array_index_cast}] {{name = \"{array_base_mlir.strip('%')}\"}} : memref<?xi64>\n"
2051
2052 vec_ptr = f"%{Node.get_mlir_id()}"
2053 mlir_str += indent + f"{vec_ptr} = llvm.inttoptr {raw_ptr} {{name = \"{array_base_mlir.strip('%')}\"}} : i64 to !llvm.ptr\n"
2054
2055 func_param_mlir = self._index.print_mlir(indent_level)
2056 # Ensure func_param is i32 (emit arith.constant for Integer, cast otherwise)
2057 if isinstance(self._index, Integer):
2058 func_param_i32 = f"%{Node.get_mlir_id()}"
2059 mlir_str += indent + f"{func_param_i32} = arith.constant {int(self._index._value)} : i32\n"
2060 elif not getattr(self._index, '_index_type', False):
2061 func_param_i32 = f"%{Node.get_mlir_id()}"
2062 mlir_str += indent + f"{func_param_i32} = arith.index_cast {func_param_mlir} : index to i32\n"
2063 else:
2064 func_param_i32 = func_param_mlir
2065
2066 result = f"%{Node.get_mlir_id()}"
2067 # Convert i32 index to i64 for pointer arithmetic
2068 func_param_i64 = f"%{Node.get_mlir_id()}"
2069 mlir_str += indent + f"{func_param_i64} = arith.extsi {func_param_i32} : i32 to i64\n"
2070 # Compute pointer to array element
2071 velem_ptr = f"%{Node.get_mlir_id()}"
2072 mlir_str += indent + f"{velem_ptr} = llvm.getelementptr {vec_ptr}[{func_param_i64}] {{name = \"{array_base_mlir.strip('%')}\"}} : (!llvm.ptr, i64) -> !llvm.ptr, f64\n"
2073 # Load the value from the computed pointer
2074 mlir_str += indent + f"{result} = llvm.load {velem_ptr} {{name = \"{array_base_mlir.strip('%')}\"}} : !llvm.ptr -> f64\n"
2075 Node.context.get_block().mlir_append(mlir_str)
2076
2077 return result
2078
2079 def print_tree(self, indent_level=0):
2080 return f"""
2081VectorIndex:
2082{self.id}
2083{self._value.print_tree(indent_level + 1)}
2084{self._index.print_tree(indent_level + 1)}"""
2085
2086
2088 def __init__(self, operation, lhs, rhs):
2089 self._operation = operation
2090 self._lhs = lhs
2091 self._rhs = rhs
2092
2093 def get_type(self):
2094 return self._lhs.get_type()
2095
2096 def print_cpp(self, indent_level = 0):
2097 return ' ' * indent_level * Node.spaces_per_tab + f"({self._lhs.print_cpp()} {self._operation} {self._rhs.print_cpp()})"
2098
2099 def print_omp(self, indent_level = 0):
2100 return ' ' * indent_level * Node.spaces_per_tab + f"({self._lhs.print_omp()} {self._operation} {self._rhs.print_omp()})"
2101
2102 def print_sycl(self, indent_level = 0):
2103 return ' ' * indent_level * Node.spaces_per_tab + f"({self._lhs.print_sycl()} {self._operation} {self._rhs.print_sycl()})"
2104
2105 @staticmethod
2106 def get_mlir_comparison_op(operation, op_type_class):
2107 if op_type_class is TFloat:
2108 op_map = {
2109 "<": "arith.cmpf olt",
2110 ">": "arith.cmpf ogt",
2111 "<=": "arith.cmpf ole",
2112 ">=": "arith.cmpf oge",
2113 "==": "arith.cmpf oeq",
2114 "!=": "arith.cmpf one"
2115 }
2116 else: # TInteger or default
2117 op_map = {
2118 "<": "arith.cmpi slt",
2119 ">": "arith.cmpi sgt",
2120 "<=": "arith.cmpi sle",
2121 ">=": "arith.cmpi sge",
2122 "==": "arith.cmpi eq",
2123 "!=": "arith.cmpi ne"
2124 }
2125 return op_map.get(operation, None)
2126
2127 def print_mlir(self, indent_level = 0):
2128 op_type_class = TDataBlock.get_mlir_type_for_operation(self._lhs, self._rhs)
2129
2130 mlir_operation = Comparison.get_mlir_comparison_op(self._operation, op_type_class)
2131 result_type = "i1" # Comparisons always return boolean
2132
2133 if mlir_operation is None:
2134 raise ValueError(f"Unsupported comparison operation: {self._operation}")
2135
2136 const_zero_id = None
2137
2138 lhs_mlir = self._lhs.print_mlir(indent_level)
2139 if TDataBlock.needs_memref_load(self._lhs):
2140 const_zero_id = TDataBlock.ensure_const_zero(const_zero_id, indent_level)
2141 lhs_mlir = TDataBlock.generate_memref_load(self._lhs, lhs_mlir, const_zero_id, indent_level)
2142
2143 rhs_mlir = self._rhs.print_mlir(indent_level)
2144 if TDataBlock.needs_memref_load(self._rhs):
2145 const_zero_id = TDataBlock.ensure_const_zero(const_zero_id, indent_level)
2146 rhs_mlir = TDataBlock.generate_memref_load(self._rhs, rhs_mlir, const_zero_id, indent_level)
2147
2148 return ' ' * indent_level * Node.spaces_per_tab + f"""{mlir_operation} {lhs_mlir}, {rhs_mlir} : {result_type}"""
2149
2150 def print_tree(self, indent_level=0):
2151 return f"""
2152Comparison {self._operation}:
2153{self._lhs.print_tree(indent_level + 1)}
2154{self._rhs.print_tree(indent_level + 1)}"""
2155
2157 def __init__(self, operation, lhs, rhs):
2158 super().__init__()
2159 self._operation = operation
2160 self._lhs = lhs
2161 self._rhs = rhs
2162
2163 if type(self._lhs) is BinaryOperation:
2164 if self._lhs._operation == "+":
2165 if self._lhs._rhs == Integer(0):
2166 self._lhs = self._lhs._lhs
2167 elif self._lhs._lhs == Integer(0):
2168 self._lhs = self._lhs._rhs
2169 elif self._lhs._operation == "-":
2170 if self._lhs._rhs == Integer(0):
2171 self._lhs = self._lhs._lhs
2172 elif self._lhs._operation == "*":
2173 if self._lhs._rhs == Integer(1):
2174 self._lhs = self._lhs._lhs
2175 elif self._lhs._lhs == Integer(1):
2176 self._lhs = self._lhs._rhs
2177
2178 if type(self._rhs) is BinaryOperation:
2179 if self._rhs._operation == "+":
2180 if self._rhs._rhs == Integer(0):
2181 self._rhs = self._rhs._lhs
2182 elif self._rhs._lhs == Integer(0):
2183 self._rhs = self._rhs._rhs
2184 elif self._rhs._operation == "-":
2185 if self._rhs._rhs == Integer(0):
2186 self._rhs = self._rhs._lhs
2187 elif self._rhs._operation == "*":
2188 if self._rhs._rhs == Integer(1):
2189 self._rhs = self._rhs._lhs
2190 elif self._rhs._lhs == Integer(1):
2191 self._rhs = self._rhs._rhs
2192
2193 if self._operation == "-" and type(self._rhs) is UnaryOperation and self._rhs._operation == "-":
2194 self._operation = "+"
2195 self._rhs = self._rhs._operand
2196 if self._operation == "+" and type(self._rhs) is UnaryOperation and self._rhs._operation == "-":
2197 self._operation = "-"
2198 self._rhs = self._rhs._operand
2199
2200 def index(self, index):
2201 return BinaryOperation(self._operation, self._lhs.index(index), self._rhs.index(index))
2202
2203 def multidimensional_index(self, index, start_index = 0, dimensions = []):
2204 return BinaryOperation(self._operation, self._lhs.multidimensional_index(index, start_index, dimensions), self._rhs.multidimensional_index(index, start_index, dimensions))
2205
2206
2207 def __add__(self, rhs):
2208 return BinaryOperation("+", self, rhs)
2209
2210 def __sub__(self, rhs):
2211 return BinaryOperation("-", self, rhs)
2212
2213 def __mul__(self, rhs):
2214 return BinaryOperation("*", self, rhs)
2215
2216 def __neg__(self):
2217 return UnaryOperation("-", self)
2218
2219 def get_type(self):
2220 # For arithmetic operations, return the element type, not the pointer type
2221 op_type_class = TDataBlock.get_mlir_type_for_operation(self._lhs, self._rhs)
2222 return op_type_class()
2223
2224 def print_cpp(self, indent_level = 0):
2225 return ' ' * indent_level * Node.spaces_per_tab + f"({self._lhs.print_cpp()} {self._operation} {self._rhs.print_cpp()})"
2226
2227 def print_omp(self, indent_level = 0):
2228 return ' ' * indent_level * Node.spaces_per_tab + f"({self._lhs.print_omp()} {self._operation} {self._rhs.print_omp()})"
2229
2230 def print_sycl(self, indent_level = 0):
2231 return ' ' * indent_level * Node.spaces_per_tab + f"({self._lhs.print_sycl()} {self._operation} {self._rhs.print_sycl()})"
2232
2233 @staticmethod
2234 def get_mlir_arithmetic_op(operation, op_type_class):
2235 if op_type_class is TFloat:
2236 op_map = {
2237 "*": "arith.mulf",
2238 "/": "arith.divf",
2239 "+": "arith.addf",
2240 "-": "arith.subf"
2241 }
2242 else: # TInteger, TLong, or default
2243 op_map = {
2244 "*": "arith.muli",
2245 "/": "arith.divi",
2246 "+": "arith.addi",
2247 "-": "arith.subi"
2248 }
2249 return op_map.get(operation, None)
2250
2251 @staticmethod
2252 def cast_operand(operand_mlir, operand, result_type, indent_level):
2253 indent = ' ' * indent_level * Node.spaces_per_tab
2254 operand_type = operand.get_type()
2255
2256 if type(operand_type) is TDataBlock:
2257 operand_type = operand_type.get_element_type()
2258
2259 if isinstance(operand_type, TInteger) and result_type == TFloat:
2260 new_op = f"%{Node.get_mlir_id()}"
2261 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.sitofp {operand_mlir.strip(' ')} : i32 to f64\n")
2262 return new_op
2263 elif isinstance(operand_type, TLong) and result_type == TFloat:
2264 new_op = f"%{Node.get_mlir_id()}"
2265 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.sitofp {operand_mlir.strip(' ')} : i64 to f64\n")
2266 return new_op
2267 elif isinstance(operand_type, TFloat) and result_type == TInteger:
2268 new_op = f"%{Node.get_mlir_id()}"
2269 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.fptosi {operand_mlir.strip(' ')} : f64 to i32\n")
2270 return new_op
2271 elif isinstance(operand_type, TFloat) and result_type == TLong:
2272 new_op = f"%{Node.get_mlir_id()}"
2273 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.fptosi {operand_mlir.strip(' ')} : f64 to i64\n")
2274 return new_op
2275 elif isinstance(operand_type, TInteger) and result_type == TLong:
2276 new_op = f"%{Node.get_mlir_id()}"
2277 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.extsi {operand_mlir.strip(' ')} : i32 to i64\n")
2278 return new_op
2279 elif isinstance(operand_type, TLong) and result_type == TInteger:
2280 new_op = f"%{Node.get_mlir_id()}"
2281 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.trunci {operand_mlir.strip(' ')} : i64 to i32\n")
2282 return new_op
2283 elif operand._index_type and result_type == TFloat:
2284 new_op = f"%{Node.get_mlir_id()}"
2285 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.index_cast {operand_mlir.strip(' ')} : index to f64\n")
2286 return new_op
2287 elif operand._index_type and result_type == TInteger:
2288 new_op = f"%{Node.get_mlir_id()}"
2289 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.index_cast {operand_mlir.strip(' ')} : index to i32\n")
2290 return new_op
2291 elif operand._index_type and result_type == TLong:
2292 new_op = f"%{Node.get_mlir_id()}"
2293 Node.context.get_block().mlir_append(indent + f"{new_op} = arith.index_cast {operand_mlir.strip(' ')} : index to i64\n")
2294 return new_op
2295 return operand_mlir # No casting needed, return original operand
2296
2297
2298 def get_mlir_op(self):
2299 op_type_class = TDataBlock.get_mlir_type_for_operation(self._lhs, self._rhs)
2300 return BinaryOperation.get_mlir_arithmetic_op(self._operation, op_type_class)
2301
2302 def print_mlir(self, indent_level = 0):
2303 indent = ' ' * indent_level * Node.spaces_per_tab
2304 self._lhs.hoist(True)
2305 self._rhs.hoist(True)
2306
2307 op_type_class = TDataBlock.get_mlir_type_for_operation(self._lhs, self._rhs)
2308 result_type = op_type_class.print_mlir(0)
2309
2310 op_type_class = TDataBlock.get_mlir_type_for_operation(self._lhs, self._rhs)
2311 result_type = op_type_class().print_mlir(0)
2312
2313 const_zero_id = None
2314
2315 self._lhs.hoist(True)
2316 lhs = self._lhs.print_mlir(indent_level).strip(' ')
2317
2318 if TDataBlock.needs_memref_load(self._lhs):
2319 const_zero_id = TDataBlock.ensure_const_zero(const_zero_id, indent_level)
2320 lhs = TDataBlock.generate_memref_load(self._lhs, lhs, const_zero_id, indent_level)
2321
2322 lhs = BinaryOperation.cast_operand(lhs, self._lhs, op_type_class, indent_level)
2323
2324 self._rhs.hoist(True)
2325 rhs = self._rhs.print_mlir(indent_level).strip(' ')
2326
2327 if TDataBlock.needs_memref_load(self._rhs):
2328 const_zero_id = TDataBlock.ensure_const_zero(const_zero_id, indent_level)
2329 rhs = TDataBlock.generate_memref_load(self._rhs, rhs, const_zero_id, indent_level)
2330
2331 rhs = BinaryOperation.cast_operand(rhs, self._rhs, op_type_class, indent_level)
2332
2333 mlir_str = ''
2334 for mlir in Node.context.pop_block().get_mlir():
2335 mlir_str += indent + mlir.strip(' ')
2336
2337 self.mlir_id(f"%{Node.get_mlir_id()}")
2338
2339 mlir_str += indent + f"""{self.mlir_id()} = {self.get_mlir_op()} {lhs}, {rhs} : {result_type}\n"""
2340 Node.context.push_block()
2341 Node.context.get_block().mlir_append(mlir_str)
2342
2343 return self.mlir_id()
2344
2345 def print_tree(self, indent_level=0):
2346 return f"""
2347BinaryOperation {self._operation}:
2348{self._lhs.print_tree(indent_level + 1)}
2349{self._rhs.print_tree(indent_level + 1)}"""
2350
2351
2353 def __init__(self, operation, operand):
2354 super().__init__()
2355 self._operation = operation
2356 self._operand = operand
2357
2358 def __add__(self, rhs):
2359 return BinaryOperation("+", self, rhs)
2360
2361 def __sub__(self, rhs):
2362 return BinaryOperation("-", self, rhs)
2363
2364 def __mul__(self, rhs):
2365 return BinaryOperation("*", self, rhs)
2366
2367 def __neg__(self):
2368 if self._operation == "-":
2369 return self._operand
2370 else:
2371 return UnaryOperation("-", self._operand)
2372
2373 def get_type(self):
2374 return self._operand.get_type()
2375
2376 def print_cpp(self, indent_level = 0):
2377 return f"({self._operation} {self._operand.print_cpp()})"
2378
2379 def print_omp(self, indent_level = 0):
2380 return f"({self._operation} {self._operand.print_omp()})"
2381
2382 def print_sycl(self, indent_level = 0):
2383 return f"({self._operation} {self._operand.print_sycl()})"
2384
2385 def print_mlir(self, indent_level=0):
2386 indent = ' ' * indent_level * Node.spaces_per_tab
2387 self._operand.hoist(True)
2388
2389 operand_type = self._operand.get_type()
2390 if isinstance(operand_type, TDataBlock):
2391 element_type_class = operand_type.get_element_type()
2392 operand_type = element_type_class() if element_type_class else operand_type
2393
2394 if isinstance(operand_type, TFloat):
2395 if self._operation == "+":
2396 Node.context.get_block().mlir_append(self._operand.print_mlir(indent_level))
2397 return self.mlir_id()
2398 elif self._operation == "-":
2399 op_mlir = self._operand.print_mlir(indent_level).strip(' ')
2400 mlir_str = "".join(Node.context.get_block().pop_mlir())
2401 neg_one_id = f"%{Node.get_mlir_id()}"
2402 mlir_str += indent + f"{neg_one_id} = arith.constant -1.0 : f64\n"
2403
2404 self.mlir_id(f"%{Node.get_mlir_id()}")
2405 mlir_str += indent + f"{self.mlir_id()} = arith.mulf {op_mlir}, {neg_one_id} : f64\n"
2406 Node.context.push_block()
2407 Node.context.get_block().mlir_append(mlir_str)
2408 return self.mlir_id()
2409 else:
2410 if self._operation == "+":
2411 Node.context.get_block().mlir_append(self._operand.print_mlir(indent_level))
2412 return self.mlir_id()
2413 elif self._operation == "-":
2414 op_mlir = self._operand.print_mlir(indent_level).strip(' ')
2415 mlir_str = "".join(Node.context.get_block().pop_mlir())
2416 neg_one_id = f"%{Node.get_mlir_id()}"
2417 mlir_str += indent + f"{neg_one_id} = arith.constant -1 : i32\n"
2418
2419 self.mlir_id(f"%{Node.get_mlir_id()}")
2420 mlir_str += indent + f"{self.mlir_id()} = arith.muli {op_mlir}, {neg_one_id} : i32\n"
2421 Node.context.push_block()
2422 Node.context.get_block().mlir_append(mlir_str)
2423 return self.mlir_id()
2424
2425 def print_tree(self, indent_level=0):
2426 return f"""
2427UnaryOperation {self._operation}:
2428{self._operand.print_tree(indent_level + 1)}"""
2429
2431 def __init__(self, dataBlock, index):
2432 super().__init__()
2433 if type(dataBlock) is Name:
2434 self._dataBlock = Name.variables[dataBlock.id]
2435 else:
2436 self._dataBlock = dataBlock
2437
2438 self._index = index
2439 self._iteration_range = self._dataBlock._iteration_range
2440 self._memory_range = self._dataBlock._memory_range
2441
2442 def index(self, index):
2443 return VectorIndex(self._dataBlock.index(index), self._index)
2444
2445 def multidimensional_index(self, index, start_index = 0, dimensions = []):
2446 return VectorIndex(self._dataBlock.multidimensional_index(index, start_index, dimensions), self._index)
2447
2448 def get_type(self):
2449 return TDataBlock(self._iteration_range, String("double"))
2450
2451 def print_cpp(self, indent_level = 0):
2452 pass
2453
2454 def print_omp(self, indent_level = 0):
2455 pass
2456
2457 def print_sycl(self, indent_level = 0):
2458 pass
2459
2460 def print_mlir(self, indent_level = 0):
2461 pass
2462
2463 def print_tree(self, indent_level=0):
2464 pass
2465
2466
2467class DataBlockComparison(Expression):
2468 def __init__(self, operation, lhs, rhs):
2469 if type(lhs) is Name and type(lhs.get_type()) is TDataBlock:
2470 self._lhs = Name.variables[lhs.id]
2471 else:
2472 self._lhs = lhs
2473
2474 if type(rhs) is Name and type(rhs.get_type()) is TDataBlock:
2475 self._rhs = Name.variables[rhs.id]
2476 else:
2477 self._rhs = rhs
2478
2479 self._operation = operation
2481 self._offset = None
2482
2483 if type(self._lhs.get_type()) is TDataBlock and type(self._rhs.get_type()) is TDataBlock:
2484 self._iteration_range = copy.deepcopy(self._lhs._iteration_range)
2485 self._memory_range = copy.deepcopy(self._lhs._memory_range)
2486 if type(self._rhs._iteration_range[0][1]) is not BinaryOperation and type(self._iteration_range[0][1]) is not BinaryOperation and self._rhs._iteration_range[0][1] > self._iteration_range[0][1]:
2487 self._iteration_range[0][1] = self._rhs._iteration_range[0][1]
2488 elif type(self._iteration_range[0][1]) is not BinaryOperation and self._iteration_range[0][1] == Integer(1):
2489 self._iteration_range[0][1] = self._rhs._iteration_range[0][1]
2490 if type(self._rhs._memory_range[0]) is not BinaryOperation and type(self._memory_range[0]) is not BinaryOperation and self._rhs._memory_range[0][1] > self._memory_range[0][1]:
2491 self._memory_range[0] = self._rhs._memory_range[0]
2492 elif type(self._memory_range[0]) is not BinaryOperation and self._memory_range[0] == Integer(1):
2493 self._memory_range[0] = self._rhs._memory_range[0]
2494
2495 elif type(self._lhs.get_type()) is TDataBlock:
2496 self._iteration_range = copy.deepcopy(self._lhs._iteration_range)
2497 self._memory_range = copy.deepcopy(self._lhs._memory_range)
2498 else:
2499 self._iteration_range = copy.deepcopy(self._rhs._iteration_range)
2500 self._memory_range = copy.deepcopy(self._rhs._memory_range)
2501
2502 def index(self, index):
2503 return Comparison(self._operation, self._lhs.index(index), self._rhs.index(index))
2504
2505 def multidimensional_index(self, index, start_index = 0, dimensions = []):
2506 return Comparison(self._operation, self._lhs.multidimensional_index(index, start_index, dimensions), self._rhs.multidimensional_index(index, start_index, dimensions))
2507
2508 def get_type(self):
2509 return TDataBlock(self._iteration_range, String("int"))
2510
2511 def print_cpp(self, indent_level = 0):
2512 pass
2513
2514 def print_omp(self, indent_level = 0):
2515 pass
2516
2517 def print_sycl(self, indent_level = 0):
2518 pass
2519
2520 def print_mlir(self, indent_level = 0):
2521 pass
2522
2523 def print_tree(self, indent_level=0):
2524 pass
2525
2527 def __init__(self, operation, lhs, rhs, useFunctionSyntax=False):
2528 super().__init__()
2529 if type(lhs) is Name and type(lhs.get_type()) is TDataBlock:
2530 self._lhs = Name.variables[lhs.id]
2531 else:
2532 self._lhs = lhs
2533
2534 if type(rhs) is Name and type(rhs.get_type()) is TDataBlock:
2535 self._rhs = Name.variables[rhs.id]
2536 else:
2537 self._rhs = rhs
2538
2539 self._operation = operation
2541 self._useFunctionSyntax = useFunctionSyntax
2542
2543 if type(self._lhs.get_type()) is TDataBlock and type(self._rhs.get_type()) is TDataBlock:
2544 self._iteration_range = copy.deepcopy(self._lhs._iteration_range)
2545 self._memory_range = copy.deepcopy(self._lhs._memory_range)
2546 if len(self._iteration_range) == 1:
2547 self._iteration_range = copy.deepcopy(self._rhs._iteration_range)
2548 elif type(self._rhs._iteration_range[0][1]) is not BinaryOperation and type(self._iteration_range[0][1]) is not BinaryOperation and self._rhs._iteration_range[0][1] > self._iteration_range[0][1]:
2549 self._iteration_range[0][1] = self._rhs._iteration_range[0][1]
2550 elif type(self._iteration_range[0][1]) is not BinaryOperation and self._iteration_range[0][1] == Integer(1):
2551 self._iteration_range[0][1] = self._rhs._iteration_range[0][1]
2552
2553 self._memory_range[0] = [Integer(0), self._iteration_range[0][1] - self._iteration_range[0][0]]
2554
2555 elif type(self._lhs.get_type()) is TDataBlock:
2556 self._iteration_range = copy.deepcopy(self._lhs._iteration_range)
2557 self._memory_range = copy.deepcopy(self._lhs._memory_range)
2558 else:
2559 self._iteration_range = copy.deepcopy(self._rhs._iteration_range)
2560 self._memory_range = copy.deepcopy(self._rhs._memory_range)
2561
2562 if (type(self._iteration_range[0][0]) is Integer and type(self._iteration_range[0][1]) is Integer and (self._iteration_range[0][1] - self._iteration_range[0][0])._value == 1):
2563 self._iteration_range[0][0] = Integer(0)
2564 self._iteration_range[0][1] = Integer(1)
2565
2566 self._offset = []
2567 for [x, y] in self._iteration_range:
2568 self._offset.append(x)
2569
2570
2571 def index(self, index):
2572 if self._useFunctionSyntax:
2573 return FunctionCall(self._operation, [self._lhs.index(index), self._rhs.index(index)])
2574 else:
2575 return BinaryOperation(self._operation, self._lhs.index(index), self._rhs.index(index))
2576
2577 def multidimensional_index(self, index, start_index = 0, dimensions = []):
2578 if self._useFunctionSyntax:
2579 return FunctionCall(self._operation, [self._lhs.multidimensional_index(index, start_index, dimensions), self._rhs.multidimensional_index(index, start_index, dimensions)])
2580 else:
2581 return BinaryOperation(self._operation, self._lhs.multidimensional_index(index, start_index, dimensions), self._rhs.multidimensional_index(index, start_index, dimensions))
2582
2583 def get_type(self):
2584 lhs_is_double = False
2585 rhs_is_double = False
2586 if type(self._lhs.get_type()) is TDataBlock:
2587 lhs_is_double = (str(self._lhs.get_type().get_single()) == "double")
2588 else:
2589 lhs_is_double = (str(self._lhs.get_type()) == "double")
2590
2591 if type(self._rhs.get_type()) is TDataBlock:
2592 rhs_is_double = (str(self._rhs.get_type().get_single()) == "double")
2593 else:
2594 rhs_is_double = (str(self._rhs.get_type()) == "double")
2595
2596 if (lhs_is_double or rhs_is_double):
2597 return TDataBlock(self._iteration_range, String("double"))
2598 else:
2599 return TDataBlock(self._iteration_range, String("int"))
2600
2601 def __sub__(self, rhs):
2602 return DataBlockBinaryOperation("-", self, rhs)
2603
2604 def __add__(self, rhs):
2605 return DataBlockBinaryOperation("+", self, rhs)
2606
2607 def print_cpp(self, indent_level = 0):
2608 pass
2609
2610 def print_omp(self, indent_level = 0):
2611 pass
2612
2613 def print_sycl(self, indent_level = 0):
2614 pass
2615
2616 def print_mlir(self, indent_level = 0):
2617 pass
2618
2619 def print_tree(self, indent_level=0):
2620 pass
2621
2622
2624 def __init__(self, operation, dataBlock):
2625 super().__init__()
2626 if type(dataBlock) is Name and type(dataBlock.get_type()) is TDataBlock:
2627 self._dataBlock = Name.variables[dataBlock.id]
2628 else:
2629 self._dataBlock = dataBlock
2630
2631 self._operation = operation
2633 self._offset = None
2634
2635 self._iteration_range = copy.deepcopy(self._dataBlock._iteration_range)
2636 self._memory_range = copy.deepcopy(self._dataBlock._memory_range)
2637
2638 def index(self, index):
2639 return UnaryOperation(self._operation, self._dataBlock.index(index))
2640
2641 def multidimensional_index(self, index, start_index = 0, dimensions = []):
2642 return UnaryOperation(self._operation, self._dataBlock.multidimensional_index(index, start_index, dimensions))
2643
2644 def get_type(self):
2645 return TDataBlock(self._iteration_range, String("double"))
2646
2647 def print_cpp(self, indent_level = 0):
2648 pass
2649
2650 def print_omp(self, indent_level = 0):
2651 pass
2652
2653 def print_sycl(self, indent_level = 0):
2654 pass
2655
2656 def print_mlir(self, indent_level = 0):
2657 pass
2658
2659 def print_tree(self, indent_level=0):
2660 pass
2661
2662
2664 def __init__(self, dataBlock):
2665 if type(dataBlock) is Name:
2666 self._dataBlock = Name.variables[dataBlock.id]
2667 else:
2668 self._dataBlock = dataBlock
2669
2670 def set_output_variable(self, outputVariable):
2671 self._outputVariable = Name.variables[outputVariable.id]
2672 self._loop = For(self._dataBlock._iteration_range[-1])
2673 self._loops = [self._loop]
2674 self._loop.add_statement(Assignment(self._outputVariable.multidimensional_index([self._loop.get_iteration_variable()]), Float(0.0)))
2675 for i in range(len(self._dataBlock._iteration_range) - 2, -1, -1):
2676 self._loops.append(For(self._dataBlock._iteration_range[i]))
2677 self._loops[-2].add_statement(self._loops[-1])
2678 index = []
2679 for loop in self._loops[::-1]:
2680 index.append(loop.get_iteration_variable())
2681 self._loops[-1].add_statement(Assignment(self._outputVariable.multidimensional_index(index), Max(self._outputVariable.multidimensional_index(index), self._dataBlock.multidimensional_index(index))))
2682 for loop in self._loops:
2683 loop.close_scope()
2684
2685 def get_type(self):
2686 return TDataBlock(self._dataBlock._iteration_range, TFloat())
2687
2688 def print_cpp(self, indent_level = 0):
2689 return self._loop.print_cpp(indent_level)
2690
2691 def print_omp(self, indent_level = 0):
2692 if Node.use_accelerator:
2693 return f"""{' ' * indent_level * Node.spaces_per_tab}omp_target_memset(maxEigenvalues, 0, N * sizeof(double), targetDevice);
2694{' ' * indent_level * Node.spaces_per_tab}for (int {self._loop._iteration_variable.print_cpp()} = {self._loop._iteration_range[0].print_cpp()}; {self._loop._iteration_variable.print_cpp()} < {self._loop._iteration_range[1].print_cpp()}; {self._loop._iteration_variable.print_cpp()}++) {{
2695{' ' * (indent_level + 1) * Node.spaces_per_tab}#pragma omp target teams loop collapse(Dimensions) reduction(max:{self._outputVariable.multidimensional_index([self._loop.get_iteration_variable()]).print_omp()})
2696{self._loop._statements[1].print_omp(indent_level + 1)}
2697{' ' * indent_level * Node.spaces_per_tab}}}
2698"""
2699 return self._loop.print_omp(indent_level)
2700
2701
2702 def print_sycl(self, indent_level = 0):
2703 ranges = [f"range{i} = {loop.get_interval_size().print_sycl()};" for i, loop in enumerate(self._loops[0:3])]
2704 indexing = [f"int {loop.get_iteration_variable().print_sycl()} = index[{i}];" for i, loop in enumerate(self._loops[0:3])]
2705 statement_prints = [statement.print_sycl(indent_level + 1) for statement in self._loops[2]._statements]
2706 return os.linesep.join(ranges) + "\n" + ' ' * indent_level * Node.spaces_per_tab + f"""queue.submit([&](::sycl::handler& handler) {{handler.memset({self._outputVariable.print_sycl()}, 0, sizeof(double) * {self._loop.get_interval_size().print_sycl()}); }}).wait();
2707{' ' * indent_level * Node.spaces_per_tab}queue.submit([&](::sycl::handler& handler) {{
2708{' ' * indent_level * Node.spaces_per_tab}handler.parallel_for(::sycl::range<3>{{range0, range1, range2}}, [=](::sycl::item<3> index) {{
2709{os.linesep.join(indexing)}
2710{self._loops[3].print_sycl(indent_level + 1)}
2711{' ' * (indent_level + 1) * Node.spaces_per_tab}}});
2712{' ' * indent_level * Node.spaces_per_tab}}}).wait();"""
2713
2714 def print_mlir(self, indent_level = 0):
2715 return self._loop.print_mlir(indent_level)
2716
2717 def print_tree(self, indent_level=0):
2718 return f"""DataBlockUnaryMax:
2719{self._dataBlock.print_tree(indent_level + 1)}"""
2720
2721
2723 def __init__(self, lhs, rhs):
2724 super().__init__("max", lhs, rhs)
2725
2726 def index(self, index):
2727 return Max(self._lhs.index(index), self._rhs.index(index))
2728
2729 def multidimensional_index(self, index, start_index = 0, dimensions = []):
2730 return Max(self._lhs.multidimensional_index(index, start_index, dimensions), self._rhs.multidimensional_index(index, start_index, dimensions))
2731
2732 def print_cpp(self, indent_level = 0):
2733 pass
2734
2735 def print_omp(self, indent_level = 0):
2736 pass
2737
2738 def print_sycl(self, indent_level = 0):
2739 pass
2740
2741 def print_mlir(self, indent_level = 0):
2742 pass
2743
2744 def print_tree(self, indent_level=0):
2745 pass
2746
2747# Types
2748class TCustom(Type):
2749 def __init__(self, type_name):
2750 self._type = type_name
2751
2752 def print_cpp(self, indent_level=0):
2753 return ' ' * indent_level * Node.spaces_per_tab + self._type
2754
2755 def print_omp(self, indent_level=0):
2756 return ' ' * indent_level * Node.spaces_per_tab + self._type
2757
2758 def print_sycl(self, indent_level=0):
2759 return ' ' * indent_level * Node.spaces_per_tab + self._type
2760
2761 def print_mlir(self, indent_level=0):
2762 mlir_type = Node.get_mlir_type(self._type)
2763 if mlir_type is not None:
2764 return ' ' * indent_level * Node.spaces_per_tab + mlir_type
2765 else:
2766 # Fallback to type_map for complex types
2767 type_entry = Node.type_map[f"{self._type}"]
2768 if isinstance(type_entry, str):
2769 return ' ' * indent_level * Node.spaces_per_tab + type_entry
2770 else:
2771 # Type object (e.g., TVoid, TFloat, etc.)
2772 return ' ' * indent_level * Node.spaces_per_tab + type_entry.print_mlir(0)
2773
2774 def print_tree(self, indent_level=0):
2775 return ' ' * indent_level * Node.spaces_per_tab + "TCustom: " + self._type
2776
2778 try:
2779 return isinstance(self._type, str) and ('xmemref<' in self._type or self._type.count('memref<') > 1)
2780 except Exception:
2781 return False
2782
2783
2785 def print_cpp(self, indent_level = 0):
2786 return ' ' * indent_level * Node.spaces_per_tab + "int"
2787
2788 def print_omp(self, indent_level = 0):
2789 return ' ' * indent_level * Node.spaces_per_tab + "int"
2790
2791 def print_sycl(self, indent_level = 0):
2792 return ' ' * indent_level * Node.spaces_per_tab + "int"
2793
2794 def print_mlir(self, indent_level = 0):
2795 return ' ' * indent_level * Node.spaces_per_tab + "i32"
2796
2797 def print_tree(self, indent_level=0):
2798 return ' ' * indent_level * Node.spaces_per_tab + "TInteger"
2799
2800
2802 """Type for 64-bit integers (long) used in MLIRCellData struct fields"""
2803 def print_cpp(self, indent_level = 0):
2804 return ' ' * indent_level * Node.spaces_per_tab + "long"
2805
2806 def print_omp(self, indent_level = 0):
2807 return ' ' * indent_level * Node.spaces_per_tab + "long"
2808
2809 def print_sycl(self, indent_level = 0):
2810 return ' ' * indent_level * Node.spaces_per_tab + "long"
2811
2812 def print_mlir(self, indent_level = 0):
2813 return ' ' * indent_level * Node.spaces_per_tab + "i64"
2814
2815 def print_tree(self, indent_level=0):
2816 return ' ' * indent_level * Node.spaces_per_tab + "TLong"
2817
2818
2820 def print_cpp(self, indent_level = 0):
2821 return ' ' * indent_level * Node.spaces_per_tab + "bool"
2822
2823 def print_omp(self, indent_level = 0):
2824 return ' ' * indent_level * Node.spaces_per_tab + "bool"
2825
2826 def print_sycl(self, indent_level = 0):
2827 return ' ' * indent_level * Node.spaces_per_tab + "bool"
2828
2829 def print_mlir(self, indent_level = 0):
2830 return ' ' * indent_level * Node.spaces_per_tab + "i1"
2831
2832 def print_tree(self, indent_level=0):
2833 return ' ' * indent_level * Node.spaces_per_tab + "TBoolean"
2834
2835
2837 def __init__(self, reference = False):
2838 self._reference = reference
2839 def print_cpp(self, indent_level = 0):
2840 return ' ' * indent_level * Node.spaces_per_tab + "double" + ("&" if self._reference else "")
2841
2842 def print_omp(self, indent_level = 0):
2843 return ' ' * indent_level * Node.spaces_per_tab + "double" + ("&" if self._reference else "")
2844
2845 def print_sycl(self, indent_level = 0):
2846 return ' ' * indent_level * Node.spaces_per_tab + "double" + ("&" if self._reference else "")
2847
2848 def print_mlir(self, indent_level = 0):
2849 return ' ' * indent_level * Node.spaces_per_tab + "f64"
2850
2851 def print_tree(self, indent_level=0):
2852 return ' ' * indent_level * Node.spaces_per_tab + "TFloat"
2853
2854
2856 def print_cpp(self, indent_level = 0):
2857 return ' ' * indent_level * Node.spaces_per_tab + "const char*"
2858
2859 def print_omp(self, indent_level = 0):
2860 return ' ' * indent_level * Node.spaces_per_tab + "const char*"
2861
2862 def print_sycl(self, indent_level = 0):
2863 return ' ' * indent_level * Node.spaces_per_tab + "const char*"
2864
2865 def print_mlir(self, indent_level=0):
2866 pass
2867
2868 def print_tree(self, indent_level=0):
2869 return ' ' * indent_level * Node.spaces_per_tab + "TString"
2870
2871
2873 def __init__(self, element_type=TFloat()):
2874 super().__init__()
2875 self._element_type = element_type
2876
2877 def print_cpp(self, indent_level=0):
2878 return ' ' * indent_level * Node.spaces_per_tab + f'double**' # C++ representation
2879
2880 def print_mlir(self, indent_level=0):
2881 return ' ' * indent_level * Node.spaces_per_tab + f'memref<?xmemref<?x{self._element_type.print_mlir(0).strip()}>>'
2882
2883 def print_omp(self, indent_level=0):
2884 return ' ' * indent_level * Node.spaces_per_tab + f'double**'
2885
2886 def print_sycl(self, indent_level=0):
2887 return ' ' * indent_level * Node.spaces_per_tab + f'double**'
2888
2890 return True
2891
2893 return self._element_type
2894
2895
2897 def __init__(self, signature_string):
2898 super().__init__()
2899 self._signature = signature_string
2900
2901 def print_cpp(self, indent_level=0):
2902 # C++ function pointers don't have direct equivalent
2903 return ' ' * indent_level * Node.spaces_per_tab + 'void*'
2904
2905 def print_mlir(self, indent_level=0):
2906 return ' ' * indent_level * Node.spaces_per_tab + self._signature
2907
2908 def print_omp(self, indent_level=0):
2909 return ' ' * indent_level * Node.spaces_per_tab + 'void*'
2910
2911 def print_sycl(self, indent_level=0):
2912 return ' ' * indent_level * Node.spaces_per_tab + 'void*'
2913
2914
2916 def __init__(self, element_type=TFloat(), dimensions=1):
2917 super().__init__()
2918 self._element_type = element_type
2919 self._dimensions = dimensions
2920
2921 def print_cpp(self, indent_level=0):
2922 return ' ' * indent_level * Node.spaces_per_tab + 'void*'
2923
2924 def print_mlir(self, indent_level=0):
2925 # Build memref<?x...xf64> with appropriate number of dimensions
2926 dims = 'x'.join(['?'] * self._dimensions)
2927 return ' ' * indent_level * Node.spaces_per_tab + f'memref<{dims}x{self._element_type.print_mlir(0).strip()}>' # Use print_mlir for element type
2928
2929 def print_omp(self, indent_level=0):
2930 return ' ' * indent_level * Node.spaces_per_tab + 'void*'
2931
2932 def print_sycl(self, indent_level=0):
2933 return ' ' * indent_level * Node.spaces_per_tab + 'void*'
2934
2936 return self._element_type
2937
2938
2940 def __init__(self, dimensions, underlying_type):
2941 self._dimensions = dimensions
2942 self._underlying_type = underlying_type
2943
2944 @staticmethod
2945 def needs_memref_load(operand):
2946 if isinstance(operand, VectorIndex):
2947 return False
2948
2949 operand_type = operand.get_type()
2950 if isinstance(operand_type, TDataBlock):
2951 if (isinstance(operand_type._dimensions, int) and operand_type._dimensions == 1) or \
2952 (isinstance(operand_type._dimensions, list) and len(operand_type._dimensions) == 1):
2953 return True
2954
2955 if isinstance(operand, Subscript) and operand.is_nested_subscript():
2956 return True
2957
2958 return False
2959
2960 @staticmethod
2961 def generate_memref_load(operand, operand_mlir, const_zero_id, indent_level):
2962 indent = ' ' * indent_level * Node.spaces_per_tab
2963 load_id = f"%{Node.get_mlir_id()}"
2964 operand_type = operand.get_type()
2965
2966 if isinstance(operand_type, TDataBlock):
2967 # Handle nested array access (double** style arrays)
2968 if isinstance(operand, Subscript) and operand.is_nested_subscript():
2969 return operand_mlir
2970
2971 element_type_class = operand_type.get_element_type()
2972 if element_type_class is not None:
2973 element_type = element_type_class().print_mlir(0)
2974 else:
2975 element_type = "f64" # Default fallback
2976
2977 load_str = indent + f"{load_id} = memref.load {operand_mlir}[{const_zero_id}] {{name = \"{operand_mlir.strip('%')}\"}} : memref<?x{element_type}>\n"
2978 Node.context.get_block().mlir_append(load_str)
2979 return load_id
2980
2981 return operand_mlir
2982
2983 @staticmethod
2984 def ensure_const_zero(const_zero_id, indent_level):
2985 if const_zero_id is None:
2986 const_zero_id = f"%{Node.get_mlir_id()}"
2987 indent = ' ' * indent_level * Node.spaces_per_tab
2988 Node.context.get_block().mlir_append(indent + f"{const_zero_id} = arith.constant 0 : index\n")
2989 return const_zero_id
2990
2991 def get_single(self):
2992 return self._underlying_type
2993
2995 if isinstance(self._underlying_type, String):
2996 if self._underlying_type._value == "double":
2997 return TFloat
2998 elif self._underlying_type._value == "int":
2999 return TInteger
3000 else:
3001 return None
3002 else:
3003 return type(self._underlying_type)
3004
3005 @staticmethod
3007 lhs_operand_type = lhs.get_type()
3008 if type(lhs_operand_type) is TDataBlock:
3009 lhs_type = lhs_operand_type.get_element_type()
3010 else:
3011 lhs_type = type(lhs_operand_type)
3012
3013 rhs_operand_type = rhs.get_type()
3014 if type(rhs_operand_type) is TDataBlock:
3015 rhs_type = rhs_operand_type.get_element_type()
3016 else:
3017 rhs_type = type(rhs_operand_type)
3018
3019 # Use float if either operand is float
3020 if lhs_type is TFloat or rhs_type is TFloat:
3021 return TFloat
3022 elif lhs_type is TLong or rhs_type is TLong:
3023 return TLong
3024 elif lhs_type is TInteger and rhs_type is TInteger:
3025 return TInteger
3026 elif lhs_type is TBoolean and rhs_type is TBoolean:
3027 return TBoolean
3028
3029 return None
3030
3032 element_type_class = self.get_element_typeget_element_type()
3033
3034 # Check for vector types by examining the underlying type
3035 if isinstance(self._underlying_type, String):
3036 is_vector = "tarch::la::Vector" in self._underlying_type._value
3037 elif hasattr(self._underlying_type, '_type'):
3038 is_vector = "tarch::la::Vector" in self._underlying_type._type
3039 else:
3040 is_vector = "tarch::la::Vector" in self._underlying_type
3041
3042 # Determine MLIR element type based on vector vs scalar
3043 if is_vector:
3044 mlir_element_type = "i64" # Vectors stored as pointers (i64)
3045 else:
3046 mlir_element_type = element_type_class().print_mlir(0)
3047
3048 return element_type_class or TFloat, is_vector, mlir_element_type
3049
3050
3051 def print_cpp(self, indent_level=0):
3052 return ' ' * indent_level * Node.spaces_per_tab + self._underlying_type.print_cpp() + "*"
3053
3054 def print_omp(self, indent_level=0):
3055 return ' ' * indent_level * Node.spaces_per_tab + self._underlying_type.print_omp() + "*"
3056
3057 def print_sycl(self, indent_level=0):
3058 return ' ' * indent_level * Node.spaces_per_tab + self._underlying_type.print_sycl() + "*"
3059
3060 def print_mlir(self, indent_level=0):
3061 dims = []
3062
3063 if isinstance(self._dimensions, int):
3064 dims.append(str(self._dimensions))
3065 else:
3066 for dim in self._dimensions:
3067 match dim:
3068 case int():
3069 dims.append(str(dim))
3070 case Expression():
3071 dims.append(dim.print_mlir(indent_level))
3072 case list():
3073 Node.context.push_block()
3074 for op in dim:
3075 op.print_mlir(indent_level)
3076 dims.append(''.join(Node.context.pop_block().get_mlir()))
3077
3078 # NOTE: The underlying type isn't an MLIR type here but a string representing the C++ type. So...
3079 # We look up the C++ type string in a types map in Node and get the MLIR type
3080 if isinstance(self._underlying_type, Type):
3081 type_name = self._underlying_type.print_mlir(indent_level)
3082 else:
3083 # We have a String type here
3084 type_name = Node.get_mlir_type(self._underlying_type._value)
3085 if type_name is None:
3086 # Fallback to type_map for complex types
3087 type_name = Node.type_map[f"{self._underlying_type}"]
3088
3089 # If type_name is already a memref type (e.g., from Vector types that map to 'memref<?xi64>'),
3090 # return it directly instead of wrapping it again. This prevents creating invalid nested memrefs like 'memref<?xmemref<?xi64>>'.
3091 if isinstance(type_name, str) and type_name.startswith("memref<"):
3092 return ' ' * indent_level * Node.spaces_per_tab + type_name
3093
3094 # Build the memref type with the correct number of dimensions
3095 # NOTE: self._dimensions may include halo dimensions, so we need to infer actual data dimensions
3096 # For now, we use a heuristic: if 1D field, return 1D; if multi-D, assume 2D (common case)
3097 # The type_map provides the correct types for named fields, so this should rarely be used
3098
3099 if isinstance(self._dimensions, int):
3100 if self._dimensions == 1:
3101 actual_dims = 1
3102 else:
3103 # Multi-dimensional iteration ranges with halo are typically 2D or 3D data
3104 # For now, assume 2D as fallback
3105 actual_dims = 2
3106 elif isinstance(self._dimensions, list):
3107 if len(self._dimensions) <= 1:
3108 actual_dims = 1
3109 else:
3110 actual_dims = 2 # Fallback for multi-dim
3111 else:
3112 actual_dims = 1 # Fallback
3113
3114 # Build memref with ? for each actual dimension
3115 dimension_list = ["?"] * actual_dims
3116 memref_dims = "x".join(dimension_list)
3117
3118 return ' ' * indent_level * Node.spaces_per_tab + f"""memref<{memref_dims}x{type_name}>"""
3119
3120 def print_tree(self, indent_level=0):
3121 return ' ' * indent_level * Node.spaces_per_tab + "TDataBlock"
3122
3123
3124def _is_nested_memref(type_map_entry):
3125 if isinstance(type_map_entry, str):
3126 # String: check for 'xmemref<' pattern
3127 return 'xmemref<' in type_map_entry
3128 elif hasattr(type_map_entry, 'is_nested_memref'):
3129 # Type object: call the method
3130 return type_map_entry.is_nested_memref()
3131 else:
3132 return False
3133
3134
3136 def print_cpp(self, indent_level = 0):
3137 pass
3138
3139 def print_omp(self, indent_level = 0):
3140 pass
3141
3142 def print_sycl(self, indent_level = 0):
3143 pass
3144
3145 def print_mlir(self, indent_level=0):
3146 pass
3147
3148 def print_tree(self, indent_level=0):
3149 return ' ' * indent_level * Node.spaces_per_tab + "TMatrix"
3150
3151
3153 def print_cpp(self, indent_level = 0):
3154 pass
3155
3156 def print_omp(self, indent_level = 0):
3157 pass
3158
3159 def print_sycl(self, indent_level = 0):
3160 pass
3161
3162 def print_mlir(self, indent_level=0):
3163 pass
3164
3165 def print_tree(self, indent_level=0):
3166 return ' ' * indent_level * Node.spaces_per_tab + "TDiagonalMatrix"
3167
3168
3170 def print_cpp(self, indent_level = 0):
3171 pass
3172
3173 def print_omp(self, indent_level = 0):
3174 pass
3175
3176 def print_sycl(self, indent_level = 0):
3177 pass
3178
3179 def print_mlir(self, indent_level=0):
3180 pass
3181
3182 def print_tree(self, indent_level=0):
3183 return ' ' * indent_level * Node.spaces_per_tab + "TVector"
3184
3185
3186# Statements
3188 def __init__(self, boolean):
3189 super().__init__()
3190 self._boolean = boolean
3191 self._statements = []
3192
3193 def add_statement(self, statement):
3194 if statement is not None:
3195 self._statements.append(statement)
3196
3197 def print_cpp(self, indent_level=0):
3198 statement_prints = [statement.print_cpp(indent_level + 1) for statement in self._statements]
3199 return ' ' * indent_level * Node.spaces_per_tab + f"""if constexpr ({self._boolean.print_cpp()}) {{
3200{os.linesep.join(statement_prints)}
3201""" + ' ' * indent_level * Node.spaces_per_tab + "}"
3202
3203 def print_omp(self, indent_level=0):
3204 statement_prints = [statement.print_omp(indent_level + 1) for statement in self._statements]
3205 return ' ' * indent_level * Node.spaces_per_tab + f"""if constexpr ({self._boolean.print_omp()}) {{
3206{os.linesep.join(statement_prints)}
3207""" + ' ' * indent_level * Node.spaces_per_tab + "}"
3208
3209 def print_sycl(self, indent_level=0):
3210 statement_prints = [statement.print_sycl(indent_level + 1) for statement in self._statements]
3211 return ' ' * indent_level * Node.spaces_per_tab + f"""if constexpr ({self._boolean.print_sycl()}) {{
3212{os.linesep.join(statement_prints)}
3213""" + ' ' * indent_level * Node.spaces_per_tab + "}"
3214
3215 def print_mlir(self, indent_level=0):
3216 indent = ' ' * indent_level * Node.spaces_per_tab
3217 Node.context.push_block()
3218 statement_prints = [statement.print_mlir(indent_level + 1) for statement in self._statements]
3219 body_statements = ''.join(Node.context.pop_block().get_mlir())
3220 body_statements += ''.join(statement_prints)
3221 return indent + f"""scf.if {self._boolean.print_mlir(indent_level)} {{
3222{body_statements}""" + indent + "}"
3223
3224
3225 def print_tree(self, indent_level=0):
3226 statement_prints = [statement.print_tree(indent_level + 1) for statement in self._statements]
3227 return ' ' * indent_level * Node.spaces_per_tab + f"""If:
3228{os.linesep.join(statement_prints)}"""
3229
3231 _inuse_iteration_variables = set()
3232 _iteration_variable_names = ['i', 'j', 'k', 'l', 'n', 'm', 'a', 'b', 'c', 'd']
3233
3234 def __init__(self, iteration_range, iteration_variable_name = None, use_scheduler = False):
3235 super().__init__()
3236 self._iteration_range = iteration_range
3237 self._statements = []
3238 self._use_scheduler = False# use_scheduler
3239
3240 if iteration_variable_name == None:
3241 for variable_name in For._iteration_variable_names:
3242 if variable_name not in For._inuse_iteration_variables:
3243 self._iteration_variable = Name(variable_name, TInteger())
3244 self._iteration_variable._index_type = True
3245 For._inuse_iteration_variables.add(variable_name)
3246 break
3247 else:
3248 self._iteration_variable = Name(iteration_variable_name, TInteger())
3249 For._inuse_iteration_variables.update({iteration_variable_name : self._iteration_variable})
3250
3252 return self._iteration_range[1] - self._iteration_range[0]
3253
3255 return self._iteration_variable
3256
3257 def add_statement(self, statement):
3258 self._statements.append(statement)
3259
3260 def close_scope(self):
3261 For._inuse_iteration_variables.remove(self._iteration_variable.id)
3262
3263 def print_cpp(self, indent_level = 0):
3264 statement_prints = [statement.print_cpp(indent_level + 1) for statement in self._statements]
3265 if self._use_scheduler:
3266 return ' ' * indent_level * Node.spaces_per_tab + f"""parallelForWithSchedulerInstructions({self._iteration_variable.print_cpp()}, {self._iteration_range[1].print_cpp()}, loopParallelism) {{
3267{os.linesep.join(statement_prints)}
3268{' ' * indent_level * Node.spaces_per_tab}}}
3269endParallelFor"""
3270 else:
3271 return ' ' * indent_level * Node.spaces_per_tab + f"""for (int {self._iteration_variable.print_cpp()} = {self._iteration_range[0].print_cpp()}; {self._iteration_variable.print_cpp()} < {self._iteration_range[1].print_cpp()}; {self._iteration_variable.print_cpp()}++) {{
3272{os.linesep.join(statement_prints)}
3273""" + ' ' * indent_level * Node.spaces_per_tab + "}"
3274
3275
3276 def print_omp(self, indent_level = 0):
3277 statement_prints = [statement.print_omp(indent_level + 1) for statement in self._statements]
3278 return ' ' * indent_level * Node.spaces_per_tab + f"""for (int {self._iteration_variable.print_omp()} = {self._iteration_range[0].print_omp()}; {self._iteration_variable.print_omp()} < {self._iteration_range[1].print_omp()}; {self._iteration_variable.print_omp()}++) {{
3279{os.linesep.join(statement_prints)}
3280""" + ' ' * indent_level * Node.spaces_per_tab + "}"
3281
3282 def print_sycl(self, indent_level = 0):
3283 statement_prints = [statement.print_sycl(indent_level + 1) for statement in self._statements]
3284 return ' ' * indent_level * Node.spaces_per_tab + f"""for (int {self._iteration_variable.print_sycl()} = {self._iteration_range[0].print_sycl()}; {self._iteration_variable.print_sycl()} < {self._iteration_range[1].print_sycl()}; {self._iteration_variable.print_sycl()}++) {{
3285{os.linesep.join(statement_prints)}
3286""" + ' ' * indent_level * Node.spaces_per_tab + "}"
3287
3288 def print_mlir(self, indent_level=0):
3289 indent = ' ' * indent_level * Node.spaces_per_tab
3290 bounds_casting = ''
3291
3292 if isinstance(self._iteration_range[0], list) or isinstance(self._iteration_range[0], tuple):
3293 dims = len(self._iteration_range)
3294 iter_vars = []
3295 lwbs = []
3296 upbs = []
3297 steps = []
3298 for d in range(dims):
3299 var = For._iteration_variable_names[d]
3300 iter_vars.append(f"%{var}")
3301 self._iteration_range[d][0].hoist(True)
3302 self._iteration_range[d][1].hoist(True)
3303 if isinstance(self._iteration_range[d][0], Integer):
3304 mlir_id = f"%{Node.get_mlir_id()}"
3305 bounds_casting += indent + f"{mlir_id} = arith.constant {self._iteration_range[d][0].print_mlir(indent_level)} : index\n"
3306 lwb = mlir_id
3307 else:
3308 lwb = self._iteration_range[d][0].print_mlir(indent_level).strip()
3309 if isinstance(self._iteration_range[d][1], Integer):
3310 mlir_id = f"%{Node.get_mlir_id()}"
3311 bounds_casting += indent + f"{mlir_id} = arith.constant {self._iteration_range[d][1].print_mlir(indent_level)} : index\n"
3312 upb = mlir_id
3313 else:
3314 upb = self._iteration_range[d][1].print_mlir(indent_level).strip()
3315 lwbs.append(lwb)
3316 upbs.append(upb)
3317 mlir_id = f"%{Node.get_mlir_id()}"
3318 bounds_casting += indent + f"{mlir_id} = arith.constant 1 : index\n"
3319 steps.append(mlir_id)
3320 else:
3321 self._iteration_range[0].hoist(True)
3322 self._iteration_range[1].hoist(True)
3323 if not self._iteration_variable._index_type:
3324 self._iteration_variable.hoist(True)
3325 mlir_str = self._iteration_variable.print_mlir(indent_level).strip()
3326 mlir_id = f"%{Node.get_mlir_id()}"
3327 bounds_casting += ''.join(Node.context.pop_block().get_mlir())
3328 Node.context.push_block()
3329 bounds_casting += indent + f"{mlir_id} = arith.index_cast {mlir_str} : {self._iteration_variable.get_type().print_mlir(0)} to index\n"
3330 iter_vars = [mlir_id]
3331 else:
3332 iter_vars = [self._iteration_variable.print_mlir(indent_level).strip()]
3333
3334 if not self._iteration_range[0]._index_type:
3335 self._iteration_range[0].hoist(True)
3336 mlir_str = self._iteration_range[0].print_mlir(indent_level).strip()
3337 mlir_id = f"%{Node.get_mlir_id()}"
3338 bounds_casting += ''.join(Node.context.pop_block().get_mlir())
3339 Node.context.push_block()
3340 bounds_casting += indent + f"{mlir_id} = arith.index_cast {mlir_str} : {self._iteration_range[0].get_type().print_mlir(indent_level).strip()} to index\n"
3341 lwbs = [mlir_id]
3342 else:
3343 lwbs += [self._iteration_range[0].print_mlir(indent_level).strip()]
3344
3345 if not self._iteration_range[1]._index_type:
3346 self._iteration_range[1].hoist(True)
3347 mlir_str = self._iteration_range[1].print_mlir(indent_level).strip()
3348 mlir_id = f"%{Node.get_mlir_id()}"
3349 bounds_casting += ''.join(Node.context.pop_block().get_mlir())
3350 Node.context.push_block()
3351 bounds_casting += indent + f"{mlir_id} = arith.index_cast {mlir_str} : {self._iteration_range[1].get_type().print_mlir(indent_level).strip()} to index\n"
3352 upbs = [mlir_id]
3353 else:
3354 upbs += [self._iteration_range[1].print_mlir(indent_level).strip()]
3355
3356 bounds_casting += ''.join(Node.context.pop_block().get_mlir())
3357 mlir_id = f"%{Node.get_mlir_id()}"
3358 bounds_casting += indent + f"{mlir_id} = arith.constant 1 : index\n"
3359 steps = [mlir_id]
3360
3361 # Collect body
3362 Node.context.push_block()
3363 statement_prints = [statement.print_mlir(indent_level + 1) for statement in self._statements]
3364 statements = ''.join(Node.context.pop_block().get_mlir())
3365 statements += ''.join(statement_prints)
3366
3367 # Check if loop contains memory allocation (nested memref allocation must be sequential)
3368 # Use scf.for for allocation loops, scf.parallel for computation loops
3369 has_allocation = any(isinstance(stmt, MemoryAllocation) for stmt in self._statements)
3370
3371 if has_allocation:
3372 # Use sequential scf.for for memory allocation (cannot parallelize)
3373 loop_header = (
3374 f"{indent}scf.for {', '.join(iter_vars)} = "
3375 f"{', '.join(lwbs)} to {', '.join(upbs)} step {', '.join(steps)} {{\n"
3376 )
3377 else:
3378 # Use scf.parallel for computation loops (safe to parallelize)
3379 loop_header = (
3380 f"{indent}scf.parallel ({', '.join(iter_vars)}) = "
3381 f"({', '.join(lwbs)}) to ({', '.join(upbs)}) step ({', '.join(steps)}) {{\n"
3382 )
3383 loop_footer = "\n" + indent + "}\n"
3384
3385 return bounds_casting + loop_header + statements + loop_footer
3386
3387 def print_tree(self, indent_level = 0):
3388 statement_prints = [statement.print_tree(indent_level + 1) for statement in self._statements]
3389 return ' ' * indent_level * Node.spaces_per_tab + f"""For:
3390{os.linesep.join(statement_prints)}"""
3391
3392
3393class Comment(Statement):
3394 def __init__(self, comment):
3395 super().__init__()
3396 self._comment = comment
3397
3398 def get_type(self):
3399 return None
3400
3401 def print_cpp(self, indent_level = 0):
3402 return ' ' * indent_level * Node.spaces_per_tab + "//" + self._comment[1:]
3403
3404 def print_omp(self, indent_level = 0):
3405 return ' ' * indent_level * Node.spaces_per_tab + "//" + self._comment[1:]
3406
3407 def print_sycl(self, indent_level = 0):
3408 return ' ' * indent_level * Node.spaces_per_tab + "//" + self._comment[1:]
3409
3410 def print_mlir(self, indent_level = 0):
3411 return ' ' * indent_level * Node.spaces_per_tab + "//" + self._comment[1:]
3412
3413 def print_tree(self, indent_level=0):
3414 return ' ' * indent_level * Node.spaces_per_tab + "Comment"
3415
3416
3417class FunctionCall(Statement):
3418 def __init__(self, id, arguments, is_offloadable = False):
3419 self.id = id
3420 self._arguments = arguments
3421 self._is_offloadable = is_offloadable
3422
3423 def add_argument(self, argument: Expression):
3424 self._arguments.append(argument)
3425
3426 def print_cpp(self, indent_level=0):
3427 argument_prints = [argument.print_cpp() for argument in self._arguments]
3428 return ' ' * indent_level * Node.spaces_per_tab + f"""{self.id}({", ".join(argument_prints)});"""
3429
3430 def print_omp(self, indent_level=0):
3431 argument_prints = [argument.print_omp() for argument in self._arguments]
3432 return ' ' * indent_level * Node.spaces_per_tab + f"""{self.id}({", ".join(argument_prints)});"""
3433
3434 def print_sycl(self, indent_level=0):
3435 argument_prints = [argument.print_sycl() for argument in self._arguments]
3436 return ' ' * indent_level * Node.spaces_per_tab + f"""{self.id}({", ".join(argument_prints)});"""
3437
3438 def print_mlir(self, indent_level=0):
3439 indent = ' ' * indent_level * Node.spaces_per_tab
3440 Node.context.push_block()
3441
3442 # Detect if this is a bridge function call
3443 is_bridge_call = self.id in ['sourceTerm', 'flux', 'nonconservativeProduct']
3444
3445 # Process arguments and handle array subscripts
3446 argument_prints = []
3447 for i, argument in enumerate(self._arguments):
3448 argument.hoist(True)
3449
3450 # Check if this is a Reference to a 2D subscript for a bridge call
3451 is_reference_to_2d_subscript = (
3452 is_bridge_call and
3453 isinstance(argument, Reference) and
3454 isinstance(argument._expression, Subscript) and
3455 isinstance(argument._expression._value, Subscript)
3456 )
3457
3458 if is_reference_to_2d_subscript:
3459 # This is Reference(Subscript[Subscript[2D_memref]])
3460 # Extract the nested structure
3461 outer_subscript = argument._expression # Subscript[Subscript[...], col_index]
3462 inner_subscript = outer_subscript._value # Subscript[2D_memref, row_index]
3463 memref_2d = inner_subscript._value
3464
3465 outer_subscript.hoist(True)
3466 outer_mlir = outer_subscript.print_mlir(indent_level)
3467 mlir_str_from_outer = "".join(Node.context.get_block().pop_mlir())
3468 Node.context.get_block().mlir_append(mlir_str_from_outer)
3469
3470 # Get MLIR representations
3471 memref_2d.hoist(True)
3472 memref_2d_mlir = memref_2d.print_mlir(indent_level).strip(' ')
3473
3474 # Get the base memref variable name (strip % prefix)
3475 base_mlir_name = memref_2d_mlir.lstrip('%')
3476 is_nested_base = False
3477 if base_mlir_name in Node.type_map and _is_nested_memref(Node.type_map[base_mlir_name]):
3478 is_nested_base = True
3479
3480 # Use unified method from Node to generate pointer arithmetic
3481 ptr_code, final_ptr = Node.generate_pointer_to_2d_element(
3482 memref_2d_mlir, inner_subscript._new_index, outer_subscript._new_index, is_nested_base, indent_level
3483 )
3484 Node.context.get_block().mlir_append(ptr_code)
3485 arg_mlir = final_ptr
3486 else:
3487 # Not a 2D subscript for bridge call - use standard approach
3488 arg_mlir = argument.print_mlir(indent_level).strip(' ')
3489
3490 # Regular subscript (array indexing) that needs loading
3491 if not is_reference_to_2d_subscript and isinstance(argument, Subscript):
3492 # Get the base array and index
3493 base_value = arg_mlir # This is the array reference
3494 index = argument._new_index # This is the converted index
3495 base_type = argument._value.get_type()
3496
3497 if isinstance(base_type, TDataBlock):
3498 # Check if this is a multi-dimensional (2D+) DataBlock
3499 if isinstance(base_type._dimensions, int) and base_type._dimensions >= 2:
3500 # This is a flat multi-dimensional memref (like memref<?x?xf64>)
3501 # When passed to a function expecting memref<?xf64>, just pass it as-is
3502 arg_mlir = f"{base_value}[{index}]"
3503 else:
3504 # Single-dimensional DataBlock
3505 element_type_class, is_vector, mlir_element_type = base_type.get_array_element_info()
3506
3507 if is_vector:
3508 # This is an array of vectors - generate a load to get the vector pointer as i64, then cast to !llvm.ptr
3509 result_id = f"%{Node.get_mlir_id()}"
3510 load_op = indent + f"{result_id} = memref.load {base_value}[{index}] {{name = \"{base_value.strip('%')}\"}} : memref<?x{mlir_element_type}>\n"
3511 Node.context.get_block().mlir_append(load_op)
3512 # Now cast the i64 to !llvm.ptr
3513 ptr_id = f"%{Node.get_mlir_id()}"
3514 cast_op = indent + f"{ptr_id} = llvm.inttoptr {result_id} : {mlir_element_type} to !llvm.ptr\n"
3515 Node.context.get_block().mlir_append(cast_op)
3516 arg_mlir = ptr_id
3517 else:
3518 # Regular array of scalars - generate a load to get the scalar value
3519 result_id = f"%{Node.get_mlir_id()}"
3520 load_op = indent + f"{result_id} = memref.load {base_value}[{index}] {{name = \"{base_value.strip('%')}\"}} : memref<?x{mlir_element_type}>\n"
3521 Node.context.get_block().mlir_append(load_op)
3522 arg_mlir = result_id
3523 else:
3524 # Handle non-TDataBlock types (like nested memrefs)
3525 if str(base_type).startswith("memref<?xmemref<?xf64>>"):
3526 # This is an array of vectors (stored as memref of memrefs)
3527 result_id = f"%{Node.get_mlir_id()}"
3528 load_op = indent + f"{result_id} = memref.load {base_value}[{index}] {{name = \"{base_value.strip('%')}\"}} : memref<?xmemref<?xf64>>\n"
3529 Node.context.get_block().mlir_append(load_op)
3530 arg_mlir = result_id
3531 else:
3532 # For other types, just pass the reference
3533 arg_mlir = f"{base_value}[{index}]"
3534
3535 argument_prints.append(arg_mlir)
3536
3537 argument_block = Node.context.pop_block().get_mlir()
3538 for op in argument_block:
3539 Node.context.get_block().mlir_append(' ' * indent_level * Node.spaces_per_tab + op.strip(' '))
3540
3541 # NOTE: we lookup the function pointer name in type_map to get the type
3542 id = self.id[0].upper() + self.id[1:]
3543 type_entry = Node.type_map[self.id]
3544 # Handle both Type objects and strings
3545 if isinstance(type_entry, str):
3546 fn_type = type_entry
3547 else:
3548 fn_type = type_entry.print_mlir(0).strip()
3549
3550 bridge_function_name = f"{self.id}_bridge"
3551 call_statement = f"""func.call @{bridge_function_name}({", ".join(argument_prints)}) : {fn_type.strip(' ')}"""
3552
3553 Node.context.get_block().mlir_append(indent + call_statement)
3554 return ''
3555
3556 def print_tree(self, indent_level=0):
3557 argument_prints = [argument.print_tree() for argument in self._arguments]
3558 return ' ' * indent_level * Node.spaces_per_tab + f"""FunctionCall:
3559{' ' * (indent_level + 1) * Node.spaces_per_tab + ",".join(argument_prints)}"""
3560
3561
3562class Max(Statement):
3563 def __init__(self, lhs, rhs):
3564 super().__init__()
3565 self._lhs = lhs
3566 self._rhs = rhs
3567
3568 def print_cpp(self, indent_level=0):
3569 return ' ' * indent_level * Node.spaces_per_tab + f"""std::max({self._lhs.print_cpp()}, {self._rhs.print_cpp()});"""
3570
3571 def print_omp(self, indent_level=0):
3572 return ' ' * indent_level * Node.spaces_per_tab + f"""std::max({self._lhs.print_omp()}, {self._rhs.print_omp()});"""
3573
3574 def print_sycl(self, indent_level=0):
3575 return ' ' * indent_level * Node.spaces_per_tab + f"""::sycl::max({self._lhs.print_sycl()}, {self._rhs.print_sycl()});"""
3576
3577 def print_mlir(self, indent_level=0):
3578 indent = ' ' * indent_level * Node.spaces_per_tab
3579
3580 def unpack_nested_memref_access(expr, indent_level):
3581 indent = ' ' * indent_level * Node.spaces_per_tab
3582
3583 if not (isinstance(expr, Subscript) and isinstance(expr._value, Subscript)):
3584 return expr.print_mlir(indent_level).strip(' ')
3585
3586 outer_subscript = expr._value
3587
3588 if not hasattr(outer_subscript, '_new_index'):
3589 outer_subscript.print_mlir(indent_level)
3590 if not hasattr(expr, '_new_index'):
3591 expr.print_mlir(indent_level)
3592
3593 outer_index = getattr(outer_subscript, '_new_index', outer_subscript._index.print_mlir(indent_level))
3594 inner_index = getattr(expr, '_new_index', expr._index.print_mlir(indent_level))
3595
3596 inner_memref_id = f"%{Node.get_mlir_id()}"
3597
3598 # Get the outer memref's MLIR variable name and check type_map
3599 outer_memref_var = outer_subscript._value.print_mlir(indent_level).strip(' ')
3600
3601 # Try to determine the outer memref type
3602 # First, check if this variable is registered in type_map
3603 try:
3604 # Get the cpp name to check type_map
3605 outer_var_cpp = outer_subscript._value.print_cpp().strip()
3606 if outer_var_cpp in Node.type_map:
3607 type_entry = Node.type_map[outer_var_cpp]
3608 # Handle both Type objects and strings
3609 if isinstance(type_entry, str):
3610 outer_memref_type = type_entry
3611 else:
3612 # Type object - call print_mlir()
3613 outer_memref_type = type_entry.print_mlir(0).strip()
3614 else:
3615 # Fallback to default nested memref type
3616 outer_memref_type = "memref<?xmemref<?xf64>>"
3617 except:
3618 outer_memref_type = "memref<?xmemref<?xf64>>"
3619
3620 mlir_str = indent + f"{inner_memref_id} = memref.load {outer_memref_var}[{outer_index}] {{name = \"{outer_memref_var.strip('%')}\"}} : {outer_memref_type}\n"
3621 value_id = f"%{Node.get_mlir_id()}"
3622 mlir_str += indent + f"{value_id} = memref.load {inner_memref_id}[{inner_index}] {{name = \"{outer_memref_var.strip('%')}\"}} : memref<?xf64>\n"
3623 Node.context.get_block().mlir_append(mlir_str)
3624
3625 return value_id
3626
3627 Node.context.push_block()
3628
3629 if type(self._lhs.get_type()) is TDataBlock:
3630 self._lhs.hoist(True)
3631 if type(self._rhs.get_type()) is TDataBlock:
3632 self._rhs.hoist(True)
3633
3634 const_zero_id = None
3635 if isinstance(self._lhs, Subscript) and isinstance(self._lhs._value, Subscript):
3636 lhs = unpack_nested_memref_access(self._lhs, indent_level)
3637 else:
3638 lhs = self._lhs.print_mlir(indent_level).strip(' ')
3639 if TDataBlock.needs_memref_load(self._lhs):
3640 const_zero_id = TDataBlock.ensure_const_zero(const_zero_id, indent_level)
3641 lhs = TDataBlock.generate_memref_load(self._lhs, lhs, const_zero_id, indent_level)
3642
3643 if isinstance(self._rhs, Subscript) and isinstance(self._rhs._value, Subscript):
3644 rhs = unpack_nested_memref_access(self._rhs, indent_level)
3645 else:
3646 rhs = self._rhs.print_mlir(indent_level).strip(' ')
3647 if TDataBlock.needs_memref_load(self._rhs):
3648 const_zero_id = TDataBlock.ensure_const_zero(const_zero_id, indent_level)
3649 rhs = TDataBlock.generate_memref_load(self._rhs, rhs, const_zero_id, indent_level)
3650
3651 argument_block = Node.context.pop_block().get_mlir()
3652 for op in argument_block:
3653 Node.context.get_block().mlir_append(op)
3654
3655 # Use the unified type determination and operation mapping from TDataBlock
3656 op_type_class = TDataBlock.get_mlir_type_for_operation(self._lhs, self._rhs)
3657 result_type = op_type_class.print_mlir(0)
3658
3659 if op_type_class is TInteger:
3660 mlir_op = "arith.maxsi"
3661 else:
3662 mlir_op = "arith.maximumf" # Default to float
3663
3664 new_id = f"%{Node.get_mlir_id()}"
3665 mlir_str = indent + f"{new_id} = {mlir_op} {lhs}, {rhs} : {result_type}\n"
3666 Node.context.get_block().mlir_append(mlir_str)
3667
3668 return new_id
3669
3670 def print_tree(self, indent_level=0):
3671 return ' ' * indent_level * Node.spaces_per_tab + f"""Max:
3672{self._lhs.print_tree(indent_level + 1)}
3673{self._rhs.print_tree(indent_level + 1)}"""
3674
3675
3676class MemoryAllocation(Statement):
3677 memoryAllocated = []
3678 allocated_types = {} # Map from MLIR variable name to allocated memref type string
3679
3680 def __init__(self, name: Name, object_type: Type, dimensions, specify_type = True, add_to_stack=True):
3681 super().__init__()
3682 self._type = object_type
3683 if type(dimensions[0]) is list:
3684 self._dimensions = [dimension[1] for dimension in dimensions]
3685 else:
3686 self._dimensions = dimensions
3687 self._name = name
3688 self._specify_type = specify_type
3689
3690 if add_to_stack == True:
3691 MemoryAllocation.memoryAllocated[-1].append(self)
3692
3693 if len(self._dimensions) > 1:
3694 self._size = self._dimensions[-2]
3695 for i in range(len(self._dimensions) - 3, -1, -1):
3696 self._size = self._size * self._dimensions[i]
3697
3698 self._loop = For([Integer(0), self._dimensions[-1]])
3699 self._loop.add_statement(MemoryAllocation(Subscript(self._name, self._loop.get_iteration_variable()), self._type, [self._size], False, False))
3700 self._loop.close_scope()
3701
3702 def print_cpp(self, indent_level=0):
3703 if len(self._dimensions) == 1:
3704 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_cpp() + "* " if self._specify_type else ""}{self._name.print_cpp()} = new {self._type.print_cpp()}[{self._dimensions[0].print_cpp()}];"""
3705 else:
3706 if Node.use_accelerator == False and type(Name.variables[self._name.id]) is not FaceDataBlock:
3707 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_cpp()}** {self._name.print_cpp()} = new {self._type.print_cpp()}*[{self._dimensions[-1].print_cpp()}];
3708{self._loop.print_cpp(indent_level)}"""
3709 else:
3710 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_cpp()}* {self._name.print_cpp()} = new {self._type.print_cpp()}[{(self._size * self._dimensions[-1]).print_cpp()}];"""
3711
3712
3713 def print_omp(self, indent_level=0):
3714 if Node.use_accelerator == False:
3715 if len(self._dimensions) == 1:
3716 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_omp() + "* " if self._specify_type else ""}{self._name.print_omp()} = new {self._type.print_omp()}[{self._dimensions[0].print_omp()}];"""
3717 else:
3718 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_omp()}** {self._name.print_omp()} = new {self._type.print_omp()}*[{self._dimensions[-1].print_omp()}];
3719{self._loop.print_omp(indent_level)}"""
3720 else:
3721 if len(self._dimensions) == 1:
3722 if Node.use_memory_manager == True:
3723 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_omp() + "* " if self._specify_type else ""}{self._name.print_omp()} = tarch::accelerator::GPUMemoryManager::getInstance().allocate<{self._type.print_omp()}>({self._dimensions[0].print_omp()}, targetDevice);"""
3724 else:
3725 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_omp() + "* " if self._specify_type else ""}{self._name.print_omp()} = ({self._type.print_omp()}*)omp_target_alloc(sizeof({self._type.print_omp()}) * {self._dimensions[0].print_omp()}, targetDevice);"""
3726 else:
3727 if Node.use_memory_manager == True:
3728 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_omp()}* {self._name.print_omp()} = tarch::accelerator::GPUMemoryManager::getInstance().allocate<{self._type.print_omp()}>({(self._size * self._dimensions[-1]).print_omp()}, targetDevice);"""
3729 else:
3730 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_omp()}* {self._name.print_omp()} = ({self._type.print_omp()}*)omp_target_alloc(sizeof({self._type.print_omp()}) * {(self._size * self._dimensions[-1]).print_omp()}, targetDevice);"""
3731
3732 def print_sycl(self, indent_level=0):
3733 if len(self._dimensions) == 1:
3734 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_sycl() + "* " if self._specify_type else ""}{self._name.print_sycl()} = new {self._type.print_sycl()}[{self._dimensions[0].print_sycl()}];"""
3735 else:
3736 return ' ' * indent_level * Node.spaces_per_tab + f"""{self._type.print_sycl()}** {self._name.print_sycl()} = new {self._type.print_sycl()}*[{self._dimensions[-1].print_sycl()}];
3737{self._loop.print_sycl(indent_level)}"""
3738
3739 def print_mlir(self, indent_level=0):
3740 indent = ' ' * indent_level * Node.spaces_per_tab
3741
3742 # Comprehensive tracing for MemoryAllocation
3743 name_mlir = self._name.print_mlir(0)
3744 name_type = self._name.get_type()
3745 is_datablock = isinstance(name_type, TDataBlock)
3746 var_name_for_map = self._name.print_cpp().strip()
3747 in_type_map = var_name_for_map in Node.type_map
3748
3749
3750 if len(self._dimensions) == 1:
3751 # Process the dimension expression first and collect any intermediate operations
3752 Node.context.push_block()
3753 index_val = self._dimensions[0].print_mlir(indent_level)
3754 intermediate_ops = Node.context.pop_block().get_mlir()
3755
3756 # Now build the allocation operations
3757 index_id = f"%{Node.get_mlir_id()}"
3758 decl = ''.join(intermediate_ops)
3759
3760 # Add the index cast and allocation with correct source type
3761 index_type = self._dimensions[0].get_type()
3762 index_mlir_type = index_type.print_mlir(0).strip()
3763 decl += indent + f"{index_id} = arith.index_cast {index_val} : {index_mlir_type} to index\n"
3764 new_id = f"%{Node.get_mlir_id()}"
3765
3766 # For 1D allocation, we should always generate 1D memref type
3767 # Get the element type correctly
3768 name_type = self._name.get_type()
3769 if isinstance(name_type, TDataBlock):
3770 # Get the element type of the DataBlock
3771 element_type_class = name_type.get_element_type()
3772 element_type = element_type_class().print_mlir(0)
3773 mlir_type = f"memref<?x{element_type}>"
3774 else:
3775 mlir_type = name_type.print_mlir(0)
3776
3777 decl += indent + f"{new_id} = memref.alloc({index_id}) {{name = \"{name_mlir.strip('%')}\"}} : {mlir_type}\n"
3778 # Track the allocated type for deallocation
3779 MemoryAllocation.allocated_types[new_id] = mlir_type
3780
3781 # Check if this is an inner allocation that needs to be stored in an outer array
3782 # This happens when self._name is a Subscript (indicating arr[i] = allocated_inner_array)
3783 if isinstance(self._name, Subscript) and not self._specify_type:
3784 # This is an inner allocation for nested memref, add the store operation
3785 # Get the outer array name and index
3786 outer_array = self._name._value # This should be the outer array name
3787 outer_index = self._name._index
3788
3789 # Process the outer array and index expressions to collect any additional operations
3790 Node.context.push_block()
3791 outer_array_mlir = outer_array.print_mlir(indent_level)
3792 outer_index_mlir = outer_index.print_mlir(indent_level)
3793 additional_ops = Node.context.pop_block().get_mlir()
3794
3795 # Add any additional operations
3796 decl += ''.join(additional_ops)
3797
3798 # When storing a memref into a nested memref array, we need to use memref<?xmemref<?xf64>>
3799 type_mlir = Node.get_mlir_type(self._type)
3800 if type_mlir is None:
3801 type_entry = Node.type_map[self._type.print_cpp()]
3802 # Handle both Type objects and strings
3803 if isinstance(type_entry, str):
3804 type_mlir = type_entry.strip(' ')
3805 else:
3806 # Type object - call print_mlir()
3807 type_mlir = type_entry.print_mlir(0).strip()
3808 outer_type = f"memref<?xmemref<?x{type_mlir}>>"
3809
3810 # Add the store operation
3811 decl += indent + f"memref.store {new_id}, {outer_array_mlir}[{outer_index_mlir}] {{name = \"{outer_array_mlir.strip('%')}\"}} : {outer_type}"
3812
3813 return decl
3814 else:
3815 # For multi-dimensional arrays, check type_map first (ordering is important)
3816 # This allows overriding default DataBlock behavior for nested memref types
3817 var_name_for_type_check = self._name.print_cpp().strip()
3818 in_type_map = var_name_for_type_check in Node.type_map
3819
3820 # Use nested memref if in type_map, otherwise check if DataBlock flat
3821 if in_type_map:
3822 # Variable has explicit type specification - use nested memref path
3823 is_datablock = False # Force nested path
3824 actual_dims_calc = -1
3825 else:
3826 # For multi-dimensional arrays, check if this is a DataBlock
3827 # If so, use flat memref with the correct number of dimensions instead of nested memref
3828 is_datablock = isinstance(self._name.get_type(), TDataBlock)
3829
3830 # Calculate actual_dims based on DataBlock iteration_range
3831 if is_datablock:
3832 datablock_obj = self._name.get_type()
3833
3834 if hasattr(datablock_obj, '_iteration_range'):
3835 actual_dims_calc = len(datablock_obj._iteration_range)
3836 elif isinstance(datablock_obj._dimensions, int):
3837 actual_dims_calc = 1 if datablock_obj._dimensions == 1 else 2
3838 elif isinstance(datablock_obj._dimensions, list):
3839 actual_dims_calc = len(datablock_obj._dimensions) if len(datablock_obj._dimensions) > 0 else 1
3840 else:
3841 actual_dims_calc = 1
3842 else:
3843 actual_dims_calc = -1 # Not a DataBlock
3844
3845 if is_datablock and actual_dims_calc >= 2:
3846 # Multi-dimensional DataBlock case: allocate as flat N-D memref
3847 element_type_class = self._name.get_type().get_element_type()
3848 element_type = element_type_class().print_mlir(0)
3849
3850 # We need actual_dims_calc dimensions from self._dimensions
3851 # For a 2D array: _dimensions might have 2, 3, or 4 elements (depends on iteration_range structure)
3852 # Take the first actual_dims_calc elements
3853 dim_exprs = self._dimensions[:actual_dims_calc]
3854 if len(dim_exprs) < actual_dims_calc:
3855 # Fallback: if we don't have enough dimensions, use what we have
3856 dim_exprs = self._dimensions
3857
3858 intermediate_decl = ""
3859 dim_ids = []
3860
3861 for i, dim_expr in enumerate(dim_exprs):
3862 # Evaluate the dimension expression - this appends its operations to context
3863 dim_val = dim_expr.print_mlir(indent_level)
3864 dim_id = f"%{Node.get_mlir_id()}"
3865 dim_ids.append(dim_id)
3866 dim_type = dim_expr.get_type()
3867 dim_mlir_type = dim_type.print_mlir(0).strip()
3868
3869 # Generate the index cast (will be appended after dimension expressions)
3870 intermediate_decl += indent + f"{dim_id} = arith.index_cast {dim_val} : {dim_mlir_type} to index\n"
3871
3872 # These must come before the index_cast operations
3873 dim_expr_ops = Node.context.pop_block().get_mlir()
3874 intermediate_decl = ''.join(dim_expr_ops) + intermediate_decl
3875
3876 # Re-push a new block for remaining operations
3877 Node.context.push_block()
3878
3879 # Process name and allocate
3880 name_mlir = self._name.print_mlir(indent_level)
3881
3882 # Build memref type: memref<?x?x...xf64> with actual_dims_calc '?'s
3883 dimension_list = ["?"] * actual_dims_calc
3884 memref_dims = "x".join(dimension_list)
3885 alloc_args = ", ".join(dim_ids)
3886
3887 # Allocate as flat N-D memref
3888 alloc_line = indent + f"{name_mlir} = memref.alloc({alloc_args}) {{name = \"{name_mlir.strip('%')}\"}} : memref<{memref_dims}x{element_type}>\n"
3889
3890 # Track the allocated memref type so it can be used in load/store operations
3891 memref_type_full = f"memref<{memref_dims}x{element_type}>"
3892 Node.mlir_memref_types[name_mlir] = memref_type_full
3893 MemoryAllocation.allocated_types[name_mlir] = memref_type_full
3894
3895 return intermediate_decl + alloc_line
3896 else:
3897 # For non-DataBlock multi-dimensional arrays, use nested memref with loop
3898 # Use the existing loop structure created in the constructor
3899 size = self._dimensions[-2]
3900 for i in range(len(self._dimensions) - 3, -1, -1):
3901 size = size * self._dimensions[i]
3902
3903 # Process the outer dimension expression first and collect any intermediate operations
3904 Node.context.push_block()
3905 outer_size_val = self._dimensions[-1].print_mlir(indent_level)
3906 intermediate_ops = Node.context.pop_block().get_mlir()
3907
3908 # Build the allocation operations
3909 decl = ''.join(intermediate_ops)
3910
3911 # Add the index cast and allocation with correct source type
3912 outer_size_id = f"%{Node.get_mlir_id()}"
3913 outer_size_type = self._dimensions[-1].get_type()
3914 outer_size_mlir_type = outer_size_type.print_mlir(0).strip()
3915 decl += indent + f"{outer_size_id} = arith.index_cast {outer_size_val} : {outer_size_mlir_type} to index\n"
3916
3917 # Determine the correct type for the outer array
3918 # Check if this variable name is in type_map (e.g., maxEigenvaluesX, maxEigenvaluesY)
3919 var_name = self._name.print_cpp().strip()
3920
3921 # NOTE: We need to check if the variable name is in type_map first
3922 if var_name in Node.type_map:
3923 # Use the explicitly registered type from type_map
3924 type_entry = Node.type_map[var_name]
3925
3926 # Handle both Type objects and strings
3927 if isinstance(type_entry, str):
3928 outer_type = type_entry
3929 else:
3930 # Type object (e.g., TNestedMemref)
3931 outer_type = type_entry.print_mlir(0).strip()
3932 elif isinstance(self._name.get_type(), TDataBlock):
3933 # Get the element type correctly for the inner memref
3934 element_type_class = self._name.get_type().get_element_type()
3935 element_type = element_type_class().print_mlir(0)
3936 outer_type = f"memref<?xmemref<?x{element_type}>>"
3937 else:
3938 # Fallback for non-DataBlock types
3939 name_type_mlir = self._name.get_type().print_mlir(0)
3940
3941 if name_type_mlir is None:
3942 type_entry = Node.type_map[self._name.get_type().print_cpp()]
3943 # Handle both Type objects and strings
3944 if isinstance(type_entry, str):
3945 name_type_mlir = type_entry.strip(' ')
3946 else:
3947 # Type object
3948 name_type_mlir = type_entry.print_mlir(0).strip()
3949 outer_type = f"memref<?x{name_type_mlir}>"
3950
3951 # Process the name expression to collect any additional operations
3952 Node.context.push_block()
3953 name_mlir = self._name.print_mlir(indent_level)
3954 name_ops = Node.context.pop_block().get_mlir()
3955
3956 # Add any name-related operations
3957 decl += ''.join(name_ops)
3958 decl += indent + f"{name_mlir} = memref.alloc({outer_size_id}) {{name = \"{name_mlir.strip('%')}\"}} : {outer_type}\n"
3959 # Track the allocated type for deallocation
3960 MemoryAllocation.allocated_types[name_mlir] = outer_type
3961
3962 # Use the existing loop structure for inner allocations
3963 loop_mlir = self._loop.print_mlir(indent_level)
3964
3965 return decl + loop_mlir
3966
3967 def print_tree(self, indent_level=0):
3968 return ' ' * indent_level * Node.spaces_per_tab + f"""MemoryAllocation:
3969{self._name.print_tree(indent_level + 1)}"""
3970
3971
3972class MemoryDeallocation(Statement):
3973 def __init__(self, allocation: MemoryAllocation):
3974 super().__init__()
3975 self._dimensions = allocation._dimensions
3976 self._name = allocation._name
3977 self._allocated_type = None # Will store the MLIR type string when known
3978
3979 if len(self._dimensions) > 1:
3980 size = self._dimensions[-2]
3981 for i in range(len(self._dimensions) - 3, -1, -1):
3982 size = size * self._dimensions[i]
3983
3984 self._loop = For([Integer(0), self._dimensions[-1]])
3985 self._loop.add_statement(MemoryDeallocation(MemoryAllocation(Subscript(self._name, self._loop.get_iteration_variable()), None, [size], False, False)))
3986 self._loop.close_scope()
3987
3988 def print_cpp(self, indent_level=0):
3989 if len(self._dimensions) == 1:
3990 return ' ' * indent_level * Node.spaces_per_tab + f"""delete[] {self._name.print_cpp()};"""
3991 else:
3992 if Node.use_accelerator == False and type(Name.variables[self._name.id]) is not FaceDataBlock:
3993 return f"""{self._loop.print_cpp(indent_level)}
3994{' ' * indent_level * Node.spaces_per_tab}delete[] {self._name.print_cpp()};"""
3995 else:
3996 return f"""{' ' * indent_level * Node.spaces_per_tab}delete[] {self._name.print_cpp()};"""
3997
3998 def print_omp(self, indent_level=0):
3999 if Node.use_accelerator == False:
4000 if len(self._dimensions) == 1:
4001 return ' ' * indent_level * Node.spaces_per_tab + f"""delete[] {self._name.print_omp()};"""
4002 else:
4003 return f"""{self._loop.print_omp(indent_level)}
4004{' ' * indent_level * Node.spaces_per_tab}delete[] {self._name.print_omp()};"""
4005 else:
4006 if Node.use_memory_manager:
4007 return ' ' * indent_level * Node.spaces_per_tab + f"""tarch::accelerator::GPUMemoryManager::getInstance().free({self._name.print_omp()}, targetDevice);"""
4008 else:
4009 return ' ' * indent_level * Node.spaces_per_tab + f"""omp_target_free({self._name.print_omp()}, targetDevice);"""
4010
4011 def print_sycl(self, indent_level=0):
4012 return ' ' * indent_level * Node.spaces_per_tab + f"""::sycl::free({self._name.print_sycl()}, queue);"""
4013 #else:
4014 # return f"""{' ' * indent_level * Node.spaces_per_tab}::sycl::free({self._name.print_sycl()}, queue);"""
4015# return f"""{self._loop.print_sycl(indent_level)}
4016#{' ' * indent_level * Node.spaces_per_tab}::sycl::free({self._name.print_sycl()}, queue);"""
4017
4018 def print_mlir(self, indent_level=0):
4019 indent = ' ' * indent_level * Node.spaces_per_tab
4020
4021 # Get the MLIR variable name
4022 name_mlir = self._name.print_mlir(indent_level)
4023
4024 # Use the stored allocated type if available
4025 if name_mlir in MemoryAllocation.allocated_types:
4026 name_type_mlir = MemoryAllocation.allocated_types[name_mlir]
4027 elif self._allocated_type is not None:
4028 # Use the allocated type if it was set explicitly
4029 name_type_mlir = self._allocated_type
4030 else:
4031 # Fallback: try to reconstruct the type from the allocation dimensions
4032 # For nested memrefs (multi-dimensional arrays), we need memref<?xmemref<?xf64>>
4033 # For 1D arrays, we need memref<?xf64>
4034 name_element_type = self._name.get_type()
4035 element_type = name_element_type.print_mlir(0).strip()
4036
4037 if len(self._dimensions) == 1:
4038 name_type_mlir = f"memref<?x{element_type}>"
4039 else:
4040 # For multidimensional, assume nested memref
4041 name_type_mlir = f"memref<?xmemref<?x{element_type}>>"
4042
4043 return "\n" + indent + f"""memref.dealloc {name_mlir} : {name_type_mlir}\n"""
4044
4045 def print_tree(self, indent_level=0):
4046 return ' ' * indent_level * Node.spaces_per_tab + f"""MemoryDeallocation:
4047{self._name.print_tree(indent_level + 1)}"""
4048
4049
4050class Construction(Statement):
4051 def __init__(self, name: Name, expression: Expression):
4052 super().__init__()
4053 self._name = name
4054 self._name.on_left(True)
4055 self._expression = expression
4056 Name.variables.update({self._name.id: expression})
4057
4058 def print_cpp(self, indent_level = 0):
4059 return ' ' * indent_level * Node.spaces_per_tab + f"{self._name.get_type().print_cpp()} {self._name.print_cpp()} = {self._expression.print_cpp()};"
4060
4061 def print_omp(self, indent_level = 0):
4062 return ' ' * indent_level * Node.spaces_per_tab + f"{self._name.get_type().print_omp()} {self._name.print_omp()} = {self._expression.print_omp()};"
4063
4064 def print_sycl(self, indent_level = 0):
4065 return ' ' * indent_level * Node.spaces_per_tab + f"{self._name.get_type().print_sycl()} {self._name.print_sycl()} = {self._expression.print_sycl()};"
4066
4067 def print_mlir(self, indent_level = 0):
4068 indent = ' ' * indent_level * Node.spaces_per_tab
4069
4070 if self._expression._string is None:
4071 expr_mlir = self._expression.print_mlir(indent_level)
4072
4073 # Get any operations that were added to the context
4074 mlir_str = ''.join(Node.context.get_block().pop_mlir())
4075 mlir_str += indent + f"{self._name.print_mlir(indent_level)} = {expr_mlir} : {self._name.get_type().print_mlir(indent_level).strip(' ')}\n"
4076 else:
4077 # Get another variable id and load the pointer into that, then load the value into the actual variable (self)
4078 new_id = Node.get_mlir_id()
4079 mlir_str = indent + f"%{new_id} = {self._expression.print_mlir(indent_level)}\n"
4080
4081 # Use the expression's type, not the name's type
4082 expr_type = self._expression.get_type().print_mlir(indent_level).strip(' ')
4083 mlir_str += indent + f"{self._name.print_mlir(indent_level)} = llvm.load %{new_id} {{name = \"{self._name.print_mlir(indent_level).strip('%')}\"}} : !llvm.ptr -> {expr_type}\n"
4084
4085 Node.context.get_block().mlir_append(mlir_str)
4086 return ''
4087
4088 def print_tree(self, indent_level=0):
4089 return f"""
4090Construction:
4091{self._name.print_tree(indent_level + 1)}
4092{self._expression.print_tree(indent_level + 1)}"""
4093
4094
4095class DataBlockConstructionFromExisting(Statement):
4096 def __init__(self, name: Name, dataBlock: DataBlock):
4097 super().__init__()
4098 self._name = name
4099 self._dataBlock = dataBlock
4100 self._dataBlock.id = name.id
4101 Name.variables.update({self._name.id: self._dataBlock})
4102 FunctionDefinition._syclDataToCopy.append(self)
4103
4104 def print_cpp(self, indent_level = 0):
4105 type_print = self._name.get_type().print_cpp()
4106 if len(self._dataBlock._iteration_range) > 1 and Node.use_accelerator == False and type(self._dataBlock) is not FaceDataBlock:
4107 type_print += "*"
4108 return ' ' * indent_level * Node.spaces_per_tab + f"{type_print} {self._name.print_cpp()} = {self._dataBlock._internal.print_cpp()};"
4109
4110 def print_omp(self, indent_level = 0):
4111 type_print = self._name.get_type().print_omp()
4112 if len(self._dataBlock._iteration_range) > 1 and Node.use_accelerator == False and type(self._dataBlock) is not FaceDataBlock:
4113 type_print += "*"
4114 return ' ' * indent_level * Node.spaces_per_tab + f"{type_print} {self._name.print_omp()} = {self._dataBlock._internal.print_omp()};"
4115
4116 def print_sycl(self, indent_level = 0):
4117 type_print = self._name.get_type().print_sycl()
4118 if len(self._dataBlock._iteration_range) > 1 and Node.use_accelerator == False and type(self._dataBlock) is not FaceDataBlock:
4119 type_print += "*"
4120 return ' ' * indent_level * Node.spaces_per_tab + f"{type_print} {self._name.print_sycl()} = {self._dataBlock._internal.print_sycl()};"
4121# step_size = Integer(1)
4122# for d in self._dataBlock._iteration_range[0:-1]:
4123# step_size = step_size * (d[1] - d[0])
4124# size = step_size * (self._dataBlock._iteration_range[-1][1] - self._dataBlock._iteration_range[-1][0])
4125# return f"""{' ' * indent_level * Node.spaces_per_tab}auto {self._name.print_sycl()} = ::sycl::malloc_shared<double>({size.print_sycl()}, queue);
4126#{' ' * indent_level * Node.spaces_per_tab}for (int z = 0; z < {(self._dataBlock._iteration_range[-1][1]-self._dataBlock._iteration_range[-1][0]).print_sycl()}; z++){{
4127#""" + (f"""{' ' * (indent_level + 1) * Node.spaces_per_tab}queue.memcpy(&{self._name.print_sycl()}[{step_size.print_sycl()} * z], {self._dataBlock._internal.print_sycl()}[z], {step_size.print_sycl()} * sizeof({self._dataBlock._underlying_type.print_sycl()})).wait();""" if self._dataBlock._internal.print_sycl()[-4:] != "QOut" else """""") + f"""
4128#{' ' * indent_level * Node.spaces_per_tab}}}
4129#"""
4130# for d in self._dataBlock._iteration_range[0:-1]:
4131# size = size * (d[1] - d[0])
4132# return f"""{' ' * indent_level * Node.spaces_per_tab}auto {self._name.print_sycl()} = ::sycl::malloc_shared<double*>({(self._dataBlock._iteration_range[-1][1]-self._dataBlock._iteration_range[-1][0]).print_sycl()}, queue);
4133#{' ' * indent_level * Node.spaces_per_tab}for (int z = 0; z < {(self._dataBlock._iteration_range[-1][1]-self._dataBlock._iteration_range[-1][0]).print_sycl()}; z++){{
4134#{' ' * (indent_level + 1) * Node.spaces_per_tab}{self._name.print_sycl()}[z] = ::sycl::malloc_shared<double>({size.print_sycl()}, queue);
4135#""" + (f"""{' ' * (indent_level + 1) * Node.spaces_per_tab}queue.memcpy({self._name.print_sycl()}[z], {self._dataBlock._internal.print_sycl()}[z], {size.print_sycl()} * sizeof({self._dataBlock._underlying_type.print_sycl()})).wait();""" if self._dataBlock._internal.print_sycl()[-4:] != "QOut" else """""") + f"""
4136#{' ' * indent_level * Node.spaces_per_tab}}}
4137#"""
4138 return ' ' * indent_level * Node.spaces_per_tab + f"{type_print} {self._name.print_sycl()} = {self._dataBlock._internal.print_sycl()};"
4139
4140 def print_mlir(self, indent_level = 0):
4141 indent = ' ' * indent_level * Node.spaces_per_tab
4142 type_print = self._name.get_type().print_mlir(indent_level)
4143
4144 dims = []
4145 for i in range(len(self._dataBlock._iteration_range) - 3, -1, -1):
4146 dims.append(self._dataBlock._iteration_range[i])
4147
4148 if len(self._dataBlock._iteration_range) > 1:
4149 type_print = "!llvm.ptr " #+ f"{self._dataBlock._underlying_type} dims: {dims})"
4150
4151 # NOTE: We encode structs as Strings, so check here if they are a struct and generate accordingly
4152 # Set the target variable name so String.print_mlir() can use it
4153 target_var_name = self._name.print_mlir(indent_level)
4154 Node.target_variable_name = target_var_name
4155
4156 # Store the mapping from DSL name to MLIR variable name for later use in assignments
4157 # TODO: This is clumsy - it is assuming the exact flow here...
4158 dsl_name = self._dataBlock._internal._value
4159 Node.mlir_symbol_table[dsl_name] = target_var_name
4160
4161 # Generate the struct field access operations with the target variable name
4162 struct_mlir = self._dataBlock._internal.print_mlir(indent_level).strip(' ')
4163 mlir_str = ''.join(Node.context.pop_block().get_mlir())
4164
4165 # Clear the target variable name - we've finished generating the MLIR for this Node
4166 Node.target_variable_name = None
4167
4168 return mlir_str
4169
4170
4171 def print_tree(self, indent_level=0):
4172 return f"""
4173DataBlockConstructionFromExisting:
4174{self._name.print_tree(indent_level + 1)}
4175{self._dataBlock._internal.print_tree(indent_level + 1)}"""
4176
4177
4178class DataBlockConstructionFromOperation:
4179 def __init__(self, name: Name, dataBlockOperation):
4180 super().__init__()
4181 self._name = name
4182 self._dataBlock = DataBlock(dataBlockOperation._iteration_range, None, False, self._name.id, underlying_type=dataBlockOperation.get_type().get_single())
4183 Name.variables.update({self._name.id: self._dataBlock})
4184 if type(dataBlockOperation) is DataBlockBinaryOperation or type(dataBlockOperation) is DataBlockUnaryOperation or type(dataBlockOperation) is DataBlockMax or type(dataBlockOperation) is DataBlockComparison or type(dataBlockOperation._internal) is String:
4185 self._operation = dataBlockOperation
4186 else:
4187 self._operation = dataBlockOperation._internal
4188
4189 self.memoryAllocation = MemoryAllocation(self._name, self._dataBlock.get_type().get_single(), self._dataBlock._memory_range)
4190 self.assignment = DataBlockAssignment(self._name, self._operation)
4191
4192 def print_cpp(self, indent_level = 0):
4193 return f"""{self.memoryAllocation.print_cpp(indent_level)}
4194{self.assignment.print_cpp(indent_level)}"""
4195
4196 def print_omp(self, indent_level = 0):
4197 return f"""{self.memoryAllocation.print_omp(indent_level)}
4198{self.assignment.print_omp(indent_level)}"""
4199
4200 def print_sycl(self, indent_level = 0):
4201 return f"""{self.memoryAllocation.print_sycl(indent_level)}
4202{self.assignment.print_sycl(indent_level)}"""
4203
4204 def print_mlir(self, indent_level = 0):
4205 return f"""{self.memoryAllocation.print_mlir(indent_level)}
4206{self.assignment.print_mlir(indent_level)}"""
4207
4208 def print_tree(self, indent_level = 0):
4209 return ' ' * indent_level * Node.spaces_per_tab + f"""DataBlockConstructionFromOperation:
4210{self._name.print_tree(indent_level + 1)}
4211{self._dataBlock.print_tree(indent_level + 1)}"""
4212
4213
4214class DataBlockConstructionFromMatrixVectorProduct:
4215 def __init__(self, name: Name, matrix, vector):
4216 self._name = name
4217 if type(vector) is Name:
4218 self._vector = Name.variables[vector.id]
4219 else:
4220 self._vector = vector
4221
4222 if type(matrix) is Name:
4223 self._matrix = Name.variables[matrix.id]
4224 else:
4225 self._matrix = matrix
4226
4227 self._dataBlock = DataBlock(self._vector._iteration_range, None, False, self._name.id, underlying_type=vector.get_type().get_single())
4228 Name.variables.update({self._name.id: self._dataBlock})
4229
4230 self.memoryAllocation = MemoryAllocation(self._name, self._dataBlock.get_type().get_single(), self._dataBlock._memory_range)
4231
4232 element_width = self._vector._iteration_range[0][1] - self._vector._iteration_range[0][0]
4233
4234 loops = []
4235 loops.append(For([Integer(0), self._dataBlock._iteration_range[-1][1] - self._dataBlock._iteration_range[-1][0]]))
4236 loops.append(For([Integer(0), self._matrix._dimensions[0]]))
4237 loops.append(For([Integer(0), self._dataBlock._iteration_range[0][1] - self._dataBlock._iteration_range[0][0]]))
4238
4239 if Node.use_accelerator:
4240 size = Integer(1)
4241 for [a, b] in self._dataBlock._memory_range[0:-1]:
4242 size = size * (b - a)
4243 index = size * loops[0].get_iteration_variable() + (loops[1].get_iteration_variable() * element_width + loops[2].get_iteration_variable())
4244 loops[2].add_statement(Assignment(Subscript(self._dataBlock, index), Float(0.0)))
4245 else:
4246 loops[2].add_statement(Assignment(Subscript(Subscript(self._dataBlock, loops[0].get_iteration_variable()), loops[1].get_iteration_variable() * element_width + loops[2].get_iteration_variable()), Float(0.0)))
4247
4248 loops.append(For([Integer(0), self._matrix._dimensions[1]]))
4249
4250 if Node.use_accelerator:
4251 size = Integer(1)
4252 for [a, b] in self._dataBlock._memory_range[0:-1]:
4253 size = size * (b - a)
4254 index = size * loops[0].get_iteration_variable() + (loops[1].get_iteration_variable() * element_width + loops[2].get_iteration_variable())
4255 loops[3].add_statement(Assignment(Subscript(self._dataBlock, index), BinaryOperation("+", Subscript(self._dataBlock, index), BinaryOperation("*", matrix.index(loops[1].get_iteration_variable(), loops[3].get_iteration_variable()), Subscript(self._vector, loops[0].get_iteration_variable() * size + element_width * loops[3].get_iteration_variable() + loops[2].get_iteration_variable())))))
4256 else:
4257 loops[3].add_statement(Assignment(Subscript(Subscript(self._dataBlock, loops[0].get_iteration_variable()), loops[1].get_iteration_variable() * element_width + loops[2].get_iteration_variable()), BinaryOperation("+", Subscript(Subscript(self._dataBlock, loops[0].get_iteration_variable()), loops[1].get_iteration_variable() * element_width + loops[2].get_iteration_variable()), BinaryOperation("*", matrix.index(loops[1].get_iteration_variable(), loops[3].get_iteration_variable()), Subscript(Subscript(self._vector, loops[0].get_iteration_variable()), element_width * loops[3].get_iteration_variable() + loops[2].get_iteration_variable())))))
4258
4259
4260
4261 loops[2].add_statement(loops[3])
4262 loops[1].add_statement(loops[2])
4263 loops[0].add_statement(loops[1])
4264 loops[3].close_scope()
4265 loops[2].close_scope()
4266 loops[1].close_scope()
4267 loops[0].close_scope()
4268 self.assignment = loops[0]
4269
4270 def print_cpp(self, indent_level = 0):
4271 return f"""{self.memoryAllocation.print_cpp(indent_level)}
4272{self.assignment.print_cpp(indent_level)}"""
4273
4274 def print_omp(self, indent_level = 0):
4275 return f"""{self.memoryAllocation.print_omp(indent_level)}
4276{self.assignment.print_omp(indent_level)}"""
4277
4278 def print_sycl(self, indent_level = 0):
4279 return f"""{self.memoryAllocation.print_sycl(indent_level)}
4280{self.assignment.print_sycl(indent_level)}"""
4281
4282 def print_mlir(self, indent_level = 0):
4283 return f"""{self.memoryAllocation.print_mlir(indent_level)}
4284{self.assignment.print_mlir(indent_level)}"""
4285
4286 def print_tree(self, indent_level = 0):
4287 return ' ' * indent_level * Node.spaces_per_tab + f"""DataBlockConstructionFromOperation:
4288{self._name.print_tree(indent_level + 1)}
4289{self._dataBlock.print_tree(indent_level + 1)}"""
4290
4291
4292class DataBlockConstructionFromDotProduct:
4293 def __init__(self, name: Name, matrix, vector):
4294 self._name = name
4295 if type(vector) is Name:
4296 self._vector = Name.variables[vector.id]
4297 else:
4298 self._vector = vector
4299
4300 if type(matrix) is Name:
4301 self._matrix = Name.variables[matrix.id]
4302 else:
4303 self._matrix = matrix
4304
4305 iteration_range = self._vector._iteration_range
4306 iteration_range[-1][1] = Integer(1)
4307 self._dataBlock = DataBlock(self._vector._iteration_range, None, False, self._name.id, underlying_type=vector.get_type().get_single())
4308 Name.variables.update({self._name.id: self._dataBlock})
4309
4310 self.memoryAllocation = MemoryAllocation(self._name, self._dataBlock.get_type().get_single(), self._dataBlock._memory_range)
4311
4312 loops = []
4313 index = []
4314 for [a, b] in self._dataBlock._iteration_range[-1::-1]:
4315 loops.append(For([Integer(0), b - a]))
4316
4317 for loop in loops[::-1]:
4318 index.append(loop.get_iteration_variable())
4319
4320 loops[-1].add_statement(Assignment(self._dataBlock.multidimensional_index(index), Float(0.0)))
4321 for i in range(len(loops) - 1):
4322 loops[i].add_statement(loops[i + 1])
4323
4324 loops.append(For([Integer(0), self._matrix._width]))
4325 rhs_index = index.copy()
4326 rhs_index[-1] = loops[-1].get_iteration_variable()
4327 loops[-1].add_statement(Assignment(self._dataBlock.multidimensional_index(index), BinaryOperation("+", self._dataBlock.multidimensional_index(index), BinaryOperation("*", Subscript(self._matrix, rhs_index[-1]), self._vector.multidimensional_index(rhs_index)))))
4328 loops[-2].add_statement(loops[-1])
4329
4330 for loop in loops:
4331 loop.close_scope()
4332 self.assignment = loops[0]
4333
4334 def print_cpp(self, indent_level = 0):
4335 return f"""{self.memoryAllocation.print_cpp(indent_level)}
4336{self.assignment.print_cpp(indent_level)}"""
4337
4338 def print_omp(self, indent_level = 0):
4339 return f"""{self.memoryAllocation.print_omp(indent_level)}
4340{self.assignment.print_omp(indent_level)}"""
4341
4342 def print_sycl(self, indent_level = 0):
4343 return f"""{self.memoryAllocation.print_sycl(indent_level)}
4344{self.assignment.print_sycl(indent_level)}"""
4345
4346 def print_mlir(self, indent_level = 0):
4347 return f"""{self.memoryAllocation.print_mlir(indent_level)}
4348{self.assignment.print_mlir(indent_level)}"""
4349
4350 def print_tree(self, indent_level = 0):
4351 return ' ' * indent_level * Node.spaces_per_tab + f"""DataBlockConstructionFromOperation:
4352{self._name.print_tree(indent_level + 1)}
4353{self._dataBlock.print_tree(indent_level + 1)}"""
4354
4355
4356class DataBlockConstructionFromFunction(Statement):
4357 def __init__(self, name: Name, dataBlock: DataBlock):
4358 super().__init__()
4359 self._name = name
4360 self._dataBlock = dataBlock
4361 self._dataBlock.id = name.id
4362 Name.variables.update({self._name.id: self._dataBlock})
4363
4364 self.memoryAllocation = MemoryAllocation(self._name, self._dataBlock.get_type().get_single(), self._dataBlock._memory_range)
4365 self.initialisation = For(self._dataBlock._memory_range[-1])
4366
4367 inner_loops = []
4368 for i in range(len(self._dataBlock._iteration_range) - 2, 0, -1):
4369 loop_bound = self._dataBlock._memory_range[i][1] - self._dataBlock._memory_range[i][0]
4370 inner_loop = For([Integer(0), loop_bound])
4371 inner_loops.append(inner_loop)
4372 if type(self._dataBlock._internal) is not FunctionCall:
4373 inner_loop = For(self._dataBlock._memory_range[0])
4374 inner_loops.append(inner_loop)
4375
4376 index = []
4377 for i in range(len(inner_loops)):
4378 index.append(inner_loops[-(i + 1)].get_iteration_variable())
4379 index.append(self.initialisation.get_iteration_variable())
4380
4381 output_offset = [Integer(0) for i in self._dataBlock._iteration_range[1:]]
4382 if type(self._dataBlock._internal._arguments[0]) is Index:
4383 input_offset = [0 for i in range(1, len(self._dataBlock._iteration_range))]
4384 else:
4385 input_offset = [self._dataBlock._iteration_range[i][0] - Name.variables[self._dataBlock._internal._arguments[0].id]._iteration_range[i][0] for i in range(1, len(self._dataBlock._iteration_range))]
4386
4387 if type(self._dataBlock._internal) is FunctionCall:
4388 functionCall = FunctionCall(self._dataBlock._internal.id, [])
4389 functionCall._is_offloadable = self._dataBlock._internal._is_offloadable
4390
4391 if type(self._dataBlock._internal._arguments[0]) is Index:
4392 functionCall.add_argument(Vector(index[0:-1], TInteger()))
4393 else:
4394 functionCall.add_argument(Reference(self._dataBlock._internal._arguments[0].multidimensional_index(index, 1)))
4395
4396 for argument in self._dataBlock._internal._arguments[1:]:
4397 if type(argument.get_type()) is TDataBlock:
4398 offset = [i[0] for i in Name.variables[argument.id]._iteration_range[1:]]
4399 if len(Name.variables[argument.id]._iteration_range) == 1 or Name.variables[argument.id]._iteration_range[0][1] == Integer(1):
4400 functionCall.add_argument(Name.variables[argument.id].multidimensional_index(index, 1))
4401 else:
4402 functionCall.add_argument(Reference(Name.variables[argument.id].multidimensional_index(index, 1)))
4403 else:
4404 functionCall.add_argument(argument)
4405
4406 functionCall.add_argument(Reference(self._dataBlock.multidimensional_index(index, 1)))
4407 if functionCall._is_offloadable:
4408 functionCall.add_argument(String("Solver::Offloadable::Yes"))
4409 else:
4410 functionCall = Assignment(self._dataBlock.multidimensional_index(index), self._dataBlock._internal)
4411
4412 self.initialisation.add_statement(inner_loops[0])
4413
4414 for i in range (0, len(inner_loops) - 1):
4415 inner_loops[i].add_statement(inner_loops[i + 1])
4416 inner_loops[-1].add_statement(functionCall)
4417
4418 for i in range(len(inner_loops) - 1, -1, -1):
4419 inner_loops[i].close_scope()
4420 self.initialisation.close_scope()
4421 self._loops = inner_loops
4422 self._loops.insert(0, self.initialisation)
4423
4424 def print_cpp(self, indent_level = 0):
4425 return f"""{self.memoryAllocation.print_cpp(indent_level)}
4426{self.initialisation.print_cpp(indent_level)}"""
4427
4428 def print_omp(self, indent_level = 0):
4429 if Node.use_accelerator == True:
4430 omp_pragma = "#pragma omp target teams loop collapse(Dimensions + 1) device(targetDevice)"
4431 else:
4432 omp_pragma = "#pragma omp parallel for simd collapse(Dimensions + 1)"
4433 return f"""{self.memoryAllocation.print_omp(indent_level)}
4434{' ' * indent_level * Node.spaces_per_tab + omp_pragma}
4435{self.initialisation.print_omp(indent_level)}"""
4436
4437 def print_sycl(self, indent_level = 0):
4438 ranges = [f"range{i} = {loop.get_interval_size().print_sycl()};" for i, loop in enumerate(self._loops[0:3])]
4439 indexing = [f"int {loop.get_iteration_variable().print_sycl()} = index[{i}];" for i, loop in enumerate(self._loops[0:3])]
4440 statement_prints = [statement.print_sycl(indent_level + 1) for statement in self._loops[2]._statements]
4441 return self.memoryAllocation.print_sycl(indent_level) + os.linesep.join(ranges) + "\n" + ' ' * indent_level * Node.spaces_per_tab + f"""queue.submit([&](::sycl::handler& handler) {{
4442{' ' * indent_level * Node.spaces_per_tab}handler.parallel_for(::sycl::range<3>{{range0, range1, range2}}, [=](::sycl::item<3> index) {{
4443{os.linesep.join(indexing)}
4444{os.linesep.join(statement_prints)}
4445{' ' * (indent_level + 1) * Node.spaces_per_tab}}});
4446{' ' * indent_level * Node.spaces_per_tab}}}).wait();"""
4447
4448
4449 def print_mlir(self, indent_level = 0):
4450 Node.context.push_block()
4451 mem_alloc = self.memoryAllocation.print_mlir(indent_level)
4452 init = self.initialisation.print_mlir(indent_level)
4453 alloc_block = Node.context.pop_block().get_mlir()
4454 for op in alloc_block:
4455 Node.context.get_block().mlir_append(op)
4456
4457 return f"""{mem_alloc}
4458{init}"""
4459
4460 def print_tree(self, indent_level = 0):
4461 return ' ' * indent_level * Node.spaces_per_tab + f"""DataBlockConstructionFromFunction:
4462{self._name.print_tree(indent_level + 1)}
4463{self._dataBlock.print_tree(indent_level + 1)}"""
4464
4465class Assignment(Statement):
4466 def __init__(self, lhs: Expression, rhs: Expression):
4467 super().__init__()
4468 self._lhs = lhs
4469 self._lhs.on_left(True)
4470 self._rhs = rhs
4471 self._rhs.on_left(False) # Just to be sure...
4472
4473 def print_cpp(self, indent_level = 0):
4474 return ' ' * indent_level * Node.spaces_per_tab + f"{self._lhs.print_cpp()} = {self._rhs.print_cpp()};"
4475
4476 def print_omp(self, indent_level = 0):
4477 return ' ' * indent_level * Node.spaces_per_tab + f"{self._lhs.print_omp()} = {self._rhs.print_omp()};"
4478
4479 def print_sycl(self, indent_level = 0):
4480 return ' ' * indent_level * Node.spaces_per_tab + f"{self._lhs.print_sycl()} = {self._rhs.print_sycl()};"
4481
4482 def print_mlir(self, indent_level = 0):
4483 indent = ' ' * indent_level * Node.spaces_per_tab
4484 mlir_str = ''
4485 Node.context.push_block()
4486 rhs = self._rhs.print_mlir(indent_level)
4487
4488 rhs_indices = []
4489 rhs_dims = []
4490 rhs_casting_mlir = ''
4491
4492 if isinstance(self._rhs, Subscript):
4493 mlir_list = Node.context.get_block().get_mlir()
4494 for i in range(self._rhs._dimensions):
4495 rhs_dims.append('?')
4496 if len(mlir_list) > 0:
4497 rhs_indices.insert(0, self._rhs._new_index)
4498 rhs_casting_mlir += mlir_list.pop()
4499
4500 mlir_str += ''.join(Node.context.pop_block().get_mlir())
4501 mlir_str += rhs_casting_mlir
4502 else:
4503 # Assume scalar here
4504 tmp = ''.join(Node.context.pop_block().get_mlir())
4505 mlir_str += tmp
4506
4507 Node.context.push_block()
4508 lhs = self._lhs.print_mlir(indent_level)
4509
4510 lhs_indices = []
4511 lhs_dims = []
4512 lhs_casting_mlir = ''
4513
4514 if isinstance(self._lhs, Subscript):
4515 mlir_list = Node.context.get_block().get_mlir()
4516 for i in range(self._lhs._dimensions):
4517 lhs_dims.append('?')
4518 if len(mlir_list) > 0:
4519 popped = mlir_list.pop()
4520 lhs_indices.insert(0, self._lhs._new_index)
4521 lhs_casting_mlir += popped
4522
4523 mlir_str += ''.join(Node.context.pop_block().get_mlir())
4524 mlir_str += lhs_casting_mlir
4525 else:
4526 tmp = ''.join(Node.context.pop_block().get_mlir())
4527 mlir_str += tmp
4528
4529 # Check if LHS is a flat 2D memref (Subscript[Subscript] on String-based field)
4530 lhs_is_flat_2d = False
4531 lhs_memref_name = None
4532 all_lhs_indices = []
4533
4534 if isinstance(self._lhs, Subscript) and isinstance(self._lhs._value, Subscript):
4535 # Check if the base is a String-based field
4536 base_base = self._lhs._value._value
4537 if isinstance(base_base, DataBlock) and isinstance(base_base._internal, String):
4538 # This is a String-based field which is now a flat 2D memref
4539 lhs_is_flat_2d = True
4540 dsl_name = base_base._internal._value
4541 lhs_memref_name = Node.mlir_symbol_table[dsl_name]
4542
4543 outer_sub = self._lhs
4544 inner_sub = self._lhs._value
4545
4546 if hasattr(inner_sub, '_new_index'):
4547 all_lhs_indices.append(inner_sub._new_index)
4548 if hasattr(outer_sub, '_new_index'):
4549 all_lhs_indices.append(outer_sub._new_index)
4550
4551 if lhs_is_flat_2d:
4552 rhs_value = rhs
4553
4554 lhs_index_str = '[' + ','.join(all_lhs_indices) + ']'
4555 lhs_type = f"memref<?x?xf64>"
4556 mlir_str += indent + f"memref.store {rhs_value}, {lhs_memref_name}{lhs_index_str} {{name = \"{lhs_memref_name.strip('%')}\"}} : {lhs_type}\n"
4557 elif isinstance(self._lhs, Subscript) and isinstance(self._lhs._value, Subscript):
4558 base_base = self._lhs._value._value
4559
4560 # If base is not a String field, it's a true nested memref (e.g. lambdaLeft[i][j])
4561 if not (isinstance(base_base, DataBlock) and isinstance(base_base._internal, String)):
4562 # This is a nested memref store (e.g., lambdaLeft[i][j] = value)
4563 rhs_value = rhs
4564 if isinstance(self._rhs, Float):
4565 const_id = f"%{Node.get_mlir_id()}"
4566 mlir_str += indent + f"{const_id} = arith.constant {self._rhs._value} : f64\n"
4567 rhs_value = const_id
4568 elif isinstance(self._rhs, Integer):
4569 const_id = f"%{Node.get_mlir_id()}"
4570 mlir_str += indent + f"{const_id} = arith.constant {self._rhs._value} : i32\n"
4571 rhs_value = const_id
4572 elif isinstance(self._rhs, Subscript):
4573 rhs_id = f"%{Node.get_mlir_id()}"
4574 rhs_index_str = '[' + ','.join(rhs_indices) + ']'
4575 rhs_type = "memref<?xf64>" if not rhs_dims else f"memref<{ 'x'.join(rhs_dims) }xf64>"
4576 mlir_str += indent + f"{rhs_id} = memref.load {rhs}{rhs_index_str} {{name = \"{rhs.strip('%')}\"}} : {rhs_type.strip(' ')}\n"
4577 rhs_value = rhs_id
4578
4579 # Extract indices from nested subscript chain
4580 # lhs._value is the inner subscript, lhs is the outer subscript
4581 outer_index = self._lhs._new_index
4582 inner_memref_var = self._lhs._value._value.print_mlir(indent_level) # Base memref name
4583
4584 # Load the inner memref from the outer nested structure
4585 inner_memref_id = f"%{Node.get_mlir_id()}"
4586 # Get the actual outer memref type from type_map
4587 base_var = self._lhs._value._value
4588 type_entry = Node.type_map[base_var.id]
4589
4590 if isinstance(type_entry, str):
4591 outer_memref_type = type_entry
4592 else:
4593 outer_memref_type = type_entry.print_mlir(0).strip()
4594
4595 # Load inner memref with first index
4596 inner_sub_index = self._lhs._value._new_index
4597 mlir_str += indent + f"{inner_memref_id} = memref.load {inner_memref_var}[{inner_sub_index}] {{name = \"{inner_memref_var.strip('%')}\"}} : {outer_memref_type}\n"
4598
4599 # Store directly into the inner memref with the second index
4600 inner_memref_type = "memref<?xf64>" # Default inner arrays are 1D
4601 mlir_str += indent + f"memref.store {rhs_value}, {inner_memref_id}[{outer_index}] {{name = \"{inner_memref_var.strip('%')}\"}} : {inner_memref_type}\n"
4602 else:
4603 # Handle regular flat memref assignment (non-nested)
4604 rhs_id = f"%{Node.get_mlir_id()}"
4605
4606 lhs_index_str = '[' + ','.join(lhs_indices) + ']'
4607 # Ensure at least one dimension for memref type
4608 if lhs_dims:
4609 lhs_type = f"memref<{ 'x'.join(lhs_dims) }xf64>"
4610 else:
4611 lhs_type = "memref<?xf64>"
4612 rhs_index_str = '[' + ','.join(rhs_indices) + ']'
4613 if rhs_dims:
4614 rhs_type = f"memref<{ 'x'.join(rhs_dims) }x f64>"
4615 else:
4616 rhs_type = "memref<?xf64>"
4617
4618 if isinstance(self._rhs, Subscript):
4619 mlir_str += indent + f"{rhs_id} = memref.load {rhs}{rhs_index_str} {{name = \"{rhs.strip('%')}\"}} : {rhs_type.strip(' ')}\n"
4620 rhs_value = rhs_id
4621 else:
4622 rhs_value = rhs
4623
4624 if isinstance(self._rhs, Float):
4625 const_id = f"%{Node.get_mlir_id()}"
4626 mlir_str += indent + f"{const_id} = arith.constant {self._rhs._value} : f64\n"
4627 rhs_value = const_id
4628 elif isinstance(self._rhs, Integer):
4629 const_id = f"%{Node.get_mlir_id()}"
4630 mlir_str += indent + f"{const_id} = arith.constant {self._rhs._value} : i32\n"
4631 rhs_value = const_id
4632
4633 if isinstance(self._lhs, Subscript):
4634 mlir_str += indent + f"memref.store {rhs_value}, {lhs}{lhs_index_str} {{name = \"{lhs.strip('%')}\"}} : {lhs_type.strip(' ')}\n"
4635 else:
4636 lhs_type_obj = self._lhs.get_type()
4637 lhs_mlir_type = lhs_type_obj.print_mlir(indent_level).strip(' ')
4638 mlir_str += indent + f"memref.store {rhs_value}, {lhs} {{name = \"{lhs.strip('%')}\"}} : {lhs_mlir_type}\n"
4639
4640 return mlir_str
4641
4642 def print_tree(self, indent_level = 0):
4643 return ' ' * indent_level * Node.spaces_per_tab + f"""Assignment:
4644{self._lhs.print_tree(indent_level + 1)}
4645{self._rhs.print_tree(indent_level + 1)}"""
4646
4647
4648class DataBlockAssignment(Statement):
4649 def __init__(self, lhs: Expression, rhs: Expression):
4650 super().__init__()
4651 if type(lhs) is Name:
4652 self._lhs = Name.variables[lhs.id]
4653 self._lhs._id = lhs.id
4654 else:
4655 self._lhs = lhs
4656
4657 if type(rhs) is Name:
4658 self._rhs = Name.variables[rhs.id]
4659 else:
4660 self._rhs = rhs
4661
4662 self._lhs.on_left(True)
4663 self._rhs.on_left(False) # Just to be sure...
4664
4665 self._loops = []
4666 index = []
4667
4668 self._loops.append(For([Integer(0), self._lhs._iteration_range[-1][1] - self._lhs._iteration_range[-1][0]]))
4669 for i in range(len(self._lhs._memory_range) - 2, -1, -1):
4670 self._loops.append(For([Integer(0), self._lhs._iteration_range[i][1] - self._lhs._iteration_range[i][0]]))
4671
4672 for i in range(len(self._lhs._memory_range) - 1, -1, -1):
4673 index.append(self._loops[i].get_iteration_variable())
4674
4675 if type(self._rhs.get_type()) is TDataBlock:
4676 rhs_offset = []
4677 for i in range(len(self._lhs._iteration_range)):
4678 lhs_start = self._lhs._iteration_range[i][0]
4679 rhs_start = self._rhs._iteration_range[i][0]
4680 if type(rhs_start.get_type()) is TDataBlock:
4681 rhs_start = Subscript(rhs_start, lhs_start)
4682 rhs_offset.append(lhs_start - rhs_start)
4683 else:
4684 rhs_offset = [Integer(0) for i in self._lhs._iteration_range]
4685
4686 lhs_offset = [Integer(0) for i in self._lhs._iteration_range]
4687
4688 self._loops[-1].add_statement(Assignment(self._lhs.multidimensional_index(index, dimensions=self._lhs._memory_range), self._rhs.multidimensional_index(index, dimensions=self._lhs._memory_range)))
4689 for i in range(0, len(self._loops) - 1):
4690 self._loops[i].add_statement(self._loops[i + 1])
4691
4692 for loop in self._loops[::-1]:
4693 loop.close_scope()
4694 self._loop = self._loops[0]
4695 self._numDimensions = len(self._loops)
4696
4697 def print_cpp(self, indent_level = 0):
4698 return self._loop.print_cpp(indent_level)
4699
4700 def print_omp(self, indent_level = 0):
4701 if Node.use_accelerator == True:
4702 omp_pragma = "#pragma omp target teams loop collapse(Dimensions + 1) device(targetDevice)"
4703 else:
4704 omp_pragma = "#pragma omp parallel for simd collapse(Dimensions + 1)"
4705 return ' ' * indent_level * Node.spaces_per_tab + omp_pragma + f"""
4706{self._loop.print_omp(indent_level)}"""
4707
4708 def print_sycl(self, indent_level = 0):
4709 #return self._loop.print_sycl(indent_level)
4710 ranges = [f"range{i} = {loop.get_interval_size().print_sycl()};" for i, loop in enumerate(self._loops[0:3])]
4711 indexing = [f"int {loop.get_iteration_variable().print_sycl()} = index[{i}];" for i, loop in enumerate(self._loops[0:3])]
4712 statement_prints = [statement.print_sycl(indent_level + 1) for statement in self._loops[2]._statements]
4713 return os.linesep.join(ranges) + "\n" + ' ' * indent_level * Node.spaces_per_tab + f"""queue.submit([&](::sycl::handler& handler) {{
4714{' ' * indent_level * Node.spaces_per_tab}handler.parallel_for(::sycl::range<3>{{range0, range1, range2}}, [=](::sycl::item<3> index) {{
4715{os.linesep.join(indexing)}
4716{os.linesep.join(statement_prints)}
4717{' ' * (indent_level + 1) * Node.spaces_per_tab}}});
4718{' ' * indent_level * Node.spaces_per_tab}}}).wait();"""
4719
4720
4721 def print_mlir(self, indent_level = 0):
4722 return self._loop.print_mlir(indent_level)
4723
4724 def print_tree(self, indent_level = 0):
4725 return ' ' * indent_level * Node.spaces_per_tab + f"""DataBlockAssignment:
4726{self._lhs.print_tree(indent_level + 1)}
4727{self._rhs.print_tree(indent_level + 1)}"""
__init__(self, id, Type argument_type, namespace=None, is_function=False)
get_mlir_arithmetic_op(operation, op_type_class)
cast_operand(operand_mlir, operand, result_type, indent_level)
multidimensional_index(self, index, start_index=0, dimensions=[])
get_mlir_comparison_op(operation, op_type_class)
__init__(self, operation, lhs, rhs)
__init__(self, operation, lhs, rhs, useFunctionSyntax=False)
multidimensional_index(self, index, start_index=0, dimensions=[])
multidimensional_index(self, index, start_index=0, dimensions=[])
multidimensional_index(self, index, start_index=0, dimensions=[])
multidimensional_index(self, index, start_index=0, dimensions=[])
multidimensional_index(self, index, start_index=0, dimensions=[])
multidimensional_index(self, index_list, start_index=0, dimensions=[])
linearise_index(self, indices, dimensions, offset_start_index=0)
__init__(self, iteration_range, internal, requires_memory_allocation, id=None, underlying_type=String("double"))
multidimensional_index(self, index, start_index=0, dimensions=[])
__init__(self, width, internal, index_dimensions)
multidimensional_index(self, index_list, start_index=0, dimensions=[])
__init__(self, iteration_range, internal, requires_memory_allocation, id=None, underlying_type=String("double"))
multidimensional_index(self, index_list, start_index=0, dimensions=[])
__init__(self, value, string=None, reference=False)
__init__(self, iteration_range, iteration_variable_name=None, use_scheduler=False)
print_cpp(self, indent_level=0)
__init__(self, id, Type return_type, template=None, namespaces=[], stateless=False, top_level=False)
print_tree(self, indent_level=0)
print_mlir(self, indent_level=0)
print_sycl(self, indent_level=0)
print_omp(self, indent_level=0)
print_cpp(self, indent_level=0)
__init__(self, value, string=None)
__init__(self, statementToLog, outputStream)
__init__(self, dimensions, internal, index_dimensions)
index(self, row_index, column_index)
print_tree(self, indent_level=0)
multidimensional_index(self, index_list, start_index=0, dimensions=[])
print_mlir(self, indent_level=0)
print_cpp(self, indent_level=0)
print_omp(self, indent_level=0)
print_sycl(self, indent_level=0)
print_tree(self, indent_level=0)
create_memref_from_ptr(cls, ptr_id, target_type, indent_level=0, size_hint=None, target_var_name=None)
print_omp(self, indent_level=0)
print_mlir(self, indent_level=0)
print_cpp(self, indent_level=0)
generate_pointer_to_2d_element(memref_mlir, row_index, col_index, is_nested, indent_level=0)
create_memref_from_extracted_descriptor(cls, alloc_ptr_id, aligned_ptr_id, offset_id, sizes_0_id, sizes_1_id, strides_0_id, strides_1_id, target_type, struct_type, indent_level=0, target_var_name=None, is_2d=False)
print_sycl(self, indent_level=0)
__init__(self, Expression expression)
__init__(self, Expression value, Expression index)
ensure_const_zero(const_zero_id, indent_level)
generate_memref_load(operand, operand_mlir, const_zero_id, indent_level)
__init__(self, dimensions, underlying_type)
Type for 64-bit integers (long) used in MLIRCellData struct fields.
__init__(self, element_type=TFloat())
__init__(self, element_type=TFloat(), dimensions=1)
print_tree(self, indent_level=0)
__init__(self, Expression value, Expression index)
__init__(self, elements, element_type, string=None)