Peano
Loading...
Searching...
No Matches
MLIRPrinter.py
Go to the documentation of this file.
1import os
2import copy
3import exahype2.dslhype.SyntaxTree as SyntaxTree
4import exahype2.dslhype.Printer as Printer
5from exahype2.dslhype.printers.CPPPrinter import CPPPrinter
6
7
70
72 def __init__(self, spaces_per_tab = 2, use_omp = False):
73 self._spaces_per_tab = spaces_per_tab
74 self.use_omp = use_omp
75 self.cpp_printer = CPPPrinter(spaces_per_tab)
76
77 def get_indent(self, indent_level: int) -> str:
78 return ' ' * indent_level * self._spaces_per_tab
79
80 def print_function_definition(self, function_definition: SyntaxTree.FunctionDefinition, indent_level = 0):
81 if function_definition._top_level:
82 for argument in function_definition._arguments:
83 SyntaxTree.Node.type_map.update({argument.id: argument._namespace})
84 if SyntaxTree.Node.type_map.get(argument._type.print(self.cpp_printer)) is None:
85 SyntaxTree.Node.type_map.update({argument._type.print(self.cpp_printer): argument._namespace})
86 SyntaxTree.Node.use_accelerator = function_definition.use_accelerator
87 SyntaxTree.Node.use_mlir_omp = self.use_omp
88 indent_level += 1 # Increase indentation for MLIR
89
90 if function_definition._top_level:
91 SyntaxTree.Node.reset_mlir_ids() # Reset the MLIR id count for the top-level function
92
93 # NOTE: We used to generate MLIRbridge.cpp using the dedicated MLIRBridgeGenerator module
94 globals_mlir = []
95
96 # Add external function declarations for bridge functions using dynamic signatures
97 # Extract function signatures directly from function_definition._template (functors only, not constants)
98 if function_definition._template is not None:
99 for argument in function_definition._arguments:
100 mlir_signature = argument._namespace #SyntaxTree.Node.type_map[cpp_type_str]
101
102 # Only process functors, skip template constants like "NumberOfVolumesPerAxisInPatch"
103 if argument._is_function:
104 # Create the bridge function declaration in MLIR with _bridge suffix
105 # This matches the C++ wrapper function names (flux_bridge, etc.)
106 func_decl = f"func.func private @{argument.id}_bridge{mlir_signature}"
107 globals_mlir.append(' ' * (indent_level) * SyntaxTree.Node.spaces_per_tab + func_decl)
108
109
110 else:
111 # No template means no MLIR bridge functions are needed
112 raise ValueError(
113 "MLIR code generation requires template parameters to determine bridge function signatures. "
114 "Ensure the function has template parameters that specify the required functors."
115 )
116
117 # Add addressof/loads for globals inside the function
118 function_global_loads = []
119 if function_definition._template is not None:
120 for argument in function_definition._template:
121 # Generate globals for ALL template parameters (including boolean evaluation flags)
122 new_id = SyntaxTree.Node.get_mlir_id()
123 globals_mlir.append(' ' * (indent_level) * SyntaxTree.Node.spaces_per_tab +f"llvm.mlir.global external @{argument.id}() : {argument._type.print(self)}")
124 function_global_loads.append(' ' * (indent_level + 1) * SyntaxTree.Node.spaces_per_tab + f"%{new_id} = llvm.mlir.addressof @{argument.id} : !llvm.ptr")
125
126 # Get the MLIR type for the argument
127 arg_mlir_type = argument._type.print(self)
128
129 # Skip global loading for variables with empty types (be more thorough)
130 if arg_mlir_type and arg_mlir_type.strip() != '' and len(arg_mlir_type.strip()) > 0:
131 function_global_loads.append(' ' * (indent_level + 1) * SyntaxTree.Node.spaces_per_tab + f"%{argument.id} = llvm.load %{new_id} {{name = \"{argument.id.strip('%')}\"}} : !llvm.ptr -> {arg_mlir_type}")
132 else:
133 # For variables with empty types, just create a comment
134 function_global_loads.append(' ' * (indent_level + 1) * SyntaxTree.Node.spaces_per_tab + f"// Skipping load for {argument.id} (empty type: '{arg_mlir_type}')")
135 else:
136 globals_mlir = []
137
138 SyntaxTree.Node.context.push_block()
139 argument_prints = [argument.print(self, indent_level).strip('') for argument in function_definition._arguments]
140 SyntaxTree.Node.context.push_block()
141
142 statement_prints = []
143 for statement in function_definition._body:
144 statement_prints.append(statement.print(self, indent_level + 1))
145 statements = SyntaxTree.Node.context.pop_block().get_mlir()
146 statements += statement_prints
147
148 return_type = function_definition._return_type.print(self)
149 return_type_str = f" -> ({return_type})" if return_type != 'void' else ''
150
151 return f"""
152builtin.module {{
153{os.linesep.join(globals_mlir)}
154 func.func @{function_definition.id + "_omp" if SyntaxTree.Node.use_mlir_omp else function_definition.id}({', '.join(argument_prints)}){return_type_str} {{
155{os.linesep.join(function_global_loads)}
156{os.linesep.join(statements)}
157func.return
158 }}
159}}
160"""
161 # Non-top-level case
162 SyntaxTree.Node.context.push_block()
163 argument_prints = [argument.print(self, indent_level) for argument in function_definition._arguments]
164 SyntaxTree.Node.context.push_block()
165 statement_prints = [statement.print(self, indent_level + 1) for statement in function_definition._body]
166 statements = SyntaxTree.Node.context.pop_block().get_mlir()
167 statements += statement_prints
168 return_type = function_definition._return_type.print(self, indent_level)
169 return_type_str = f" -> ({return_type})" if return_type != 'void' else ''
170 return f"""
171 func.func @{function_definition.id}({', '.join(argument_prints)}){return_type_str} {{
172{os.linesep.join(statements)}
173func.return
174 }}
175"""
176
177 def print_function_declaration(self, function_definition: SyntaxTree.FunctionDefinition, indent_level=0):
178 if self.use_omp:
179 extra_namespace = "omp::mlir"
180 else:
181 extra_namespace = "mlir"
182 namespace_header = f"""namespace {"::".join(function_definition._namespaces)}::{extra_namespace} {{"""
183 namespace_footer = "}"
184 template_string = ""
185 if function_definition._template is not None:
186 template_strings = [argument.print(self.cpp_printer) for argument in function_definition._template]
187 template_string = "template <" + ",".join(template_strings) + ">\n"
188
189 current_indent = indent_level + 1 # Indentation increases inside namespace
190 argument_prints = [argument.print(self.cpp_printer) for argument in function_definition._arguments]
191
192 return f"""{namespace_header}
193{template_string}{self.get_indent(indent_level)}{function_definition._return_type.print(self.cpp_printer)} {function_definition.id}({', '.join(argument_prints)});
194{namespace_footer}
195"""
196
197 def print_function_definition_with_timer(self, function_definition: SyntaxTree.FunctionDefinition, indent_level=0):
198 if self.use_omp:
199 extra_namespace = "omp::mlir"
200 else:
201 extra_namespace = "mlir"
202 namespace_header = f"""namespace {"::".join(function_definition._namespaces)}::{extra_namespace} {{"""
203 namespace_footer = "}"
204 template_string = ""
205 function_call_string = function_definition.id
206 if function_definition._template is not None:
207 template_strings = [argument.print(self.cpp_printer) for argument in function_definition._template]
208 template_string = "template <" + ",".join(template_strings) + ">\n"
209 function_call_string += "<" + ",".join([template.split(' ')[1] for template in template_strings]) + ">"
210
211 arguments = copy.deepcopy(function_definition._arguments)
212 arguments.append(SyntaxTree.Argument("measurement", SyntaxTree.TCustom("tarch::timing::Measurement&")))
213
214 current_indent = indent_level + 1 # Indentation increases inside namespace
215 argument_prints = [argument.print(self.cpp_printer) for argument in arguments]
216
217 return f"""{namespace_header}
218{template_string}{self.get_indent(indent_level)}{function_definition._return_type.print(self.cpp_printer)} {function_definition.id}({', '.join(argument_prints)}) {{
219tarch::timing::Watch watch("{"::".join(function_definition._namespaces)}", "{function_definition.id}", false, true);
220{function_call_string}({",".join([argument.print(self.cpp_printer).split(' ')[-1] for argument in function_definition._arguments])});
221watch.stop();
222measurement.setValue(watch.getCalendarTime());
223{self.get_indent(indent_level)}}}
224{namespace_footer}
225"""
226
227 def print_function_declaration_with_timer(self, function_definition: SyntaxTree.FunctionDefinition, indent_level=0):
228 if self.use_omp:
229 extra_namespace = "omp::mlir"
230 else:
231 extra_namespace = "mlir"
232 namespace_header = f"""namespace {"::".join(function_definition._namespaces)}::{extra_namespace} {{"""
233 namespace_footer = "}"
234 template_string = ""
235 if function_definition._template is not None:
236 template_strings = [argument.print(self.cpp_printer) for argument in function_definition._template]
237 template_string = "template <" + ",".join(template_strings) + ">\n"
238
239 arguments = copy.deepcopy(function_definition._arguments)
240 arguments.append(SyntaxTree.Argument("measurement", SyntaxTree.TCustom("tarch::timing::Measurement&")))
241
242 current_indent = indent_level + 1 # Indentation increases inside namespace
243 argument_prints = [argument.print(self.cpp_printer) for argument in arguments]
244
245 return f"""{namespace_header}
246{template_string}{self.get_indent(indent_level)}{function_definition._return_type.print(self.cpp_printer)} {function_definition.id}({', '.join(argument_prints)});
247{namespace_footer}
248"""
249
250 def print_argument(self, argument: SyntaxTree.Argument, indent_level=0):
251 # NOTE: Arguments are inserted in-line into 'func.func' op
252 return f"%{argument.id} : {argument._type.print(self)}"
253
254 def print_name(self, name: SyntaxTree.Name, indent_level=0):
255 return f"%{name.id}"
256
257 def print_integer(self, integer: SyntaxTree.Integer, indent_level=0):
258 indent = self.get_indent(indent_level)
259 if integer._string is None:
260 value = str(integer._value)
261 if integer.hoist():
262 integer.mlir_id(SyntaxTree.Node.get_mlir_id())
263 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
264 mlir_str += indent + f"%{integer.mlir_id()} = arith.constant {integer._value} : {integer.get_type().print(self)}\n"
265 SyntaxTree.Node.context.push_block()
266 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
267 id = f"%{integer.mlir_id()}"
268 return id
269 return value
270 else:
271 # NOTE: We use Strings for the CellData structs (patchData).
272 # So, we check the name here and we generate the MLIR to
273 # extract the struct from LLVM pointer
274 # TODO: We need to raise this to where we make the assignment
275 match integer._string:
276 case x if '.' in x:
277 struct, field = x.split('.')
278 if struct in SyntaxTree.Node.struct_map:
279 struct_list = SyntaxTree.Node.struct_map[struct]
280 idx = [item[0] for item in struct_list].index(field)
281 mlir_type = struct_list[idx][1]
282 # Extract just the struct type (everything after the separator)
283 mlir_type_struct = mlir_type.split('|||')[1] if '|||' in mlir_type else mlir_type
284 # getelementptr returns !llvm.ptr and takes struct type as parameter
285 mlir_str = f"llvm.getelementptr %{struct}[0, {idx}] : (!llvm.ptr) -> !llvm.ptr, {mlir_type_struct}"
286 return mlir_str
287 return integer._string
288
289 def print_if(self, if_statement: SyntaxTree.If, indent_level=0):
290 indent = self.get_indent(indent_level)
291 SyntaxTree.Node.context.push_block()
292 statement_prints = [statement.print(self, indent_level + 1) for statement in if_statement._statements]
293 body_statements = ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
294 body_statements += ''.join(statement_prints)
295 return indent + f"""scf.if {if_statement._boolean.print(self, indent_level)} {{
296{body_statements}""" + indent + "}"
297
298 def print_for(self, for_statement: SyntaxTree.For, indent_level = 0):
299 indent = self.get_indent(indent_level)
300 bounds_casting = ''
301
302 if isinstance(for_statement._iteration_range[0], list) or isinstance(for_statement._iteration_range[0], tuple):
303 dims = len(for_statement._iteration_range)
304 iter_vars = []
305 lwbs = []
306 upbs = []
307 steps = []
308 for d in range(dims):
309 var = For._iteration_variable_names[d]
310 iter_vars.append(f"%{var}")
311 for_statement._iteration_range[d][0].hoist(True)
312 for_statement._iteration_range[d][1].hoist(True)
313 if isinstance(for_statement._iteration_range[d][0], SyntaxTree.Integer):
314 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
315 bounds_casting += indent + f"{mlir_id} = arith.constant {for_statement._iteration_range[d][0].print(self, indent_level)} : index\n"
316 lwb = mlir_id
317 else:
318 lwb = for_statement._iteration_range[d][0].print(self, indent_level).strip()
319 if isinstance(for_statement._iteration_range[d][1], SyntaxTree.Integer):
320 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
321 bounds_casting += indent + f"{mlir_id} = arith.constant {for_statement._iteration_range[d][1].print(self, indent_level)} : index\n"
322 upb = mlir_id
323 else:
324 upb = for_statement._iteration_range[d][1].print(self, indent_level).strip()
325 lwbs.append(lwb)
326 upbs.append(upb)
327 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
328 bounds_casting += indent + f"{mlir_id} = arith.constant 1 : index\n"
329 steps.append(mlir_id)
330 else:
331 for_statement._iteration_range[0].hoist(True)
332 for_statement._iteration_range[1].hoist(True)
333 if not for_statement._iteration_variable._index_type:
334 for_statement._iteration_variable.hoist(True)
335 mlir_str = for_statement._iteration_variable.print(self, indent_level).strip()
336 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
337 bounds_casting += ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
338 SyntaxTree.Node.context.push_block()
339 bounds_casting += indent + f"{mlir_id} = arith.index_cast {mlir_str} : {for_statement._iteration_variable.get_type().print(self)} to index\n"
340 iter_vars = [mlir_id]
341 else:
342 iter_vars = [for_statement._iteration_variable.print(self, indent_level).strip()]
343
344 if not for_statement._iteration_range[0]._index_type:
345 for_statement._iteration_range[0].hoist(True)
346 mlir_str = for_statement._iteration_range[0].print(self, indent_level).strip()
347 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
348 bounds_casting += ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
349 SyntaxTree.Node.context.push_block()
350 bounds_casting += indent + f"{mlir_id} = arith.index_cast {mlir_str} : {for_statement._iteration_range[0].get_type().print(self, indent_level).strip()} to index\n"
351 lwbs = [mlir_id]
352 else:
353 lwbs += [for_statement._iteration_range[0].print(self, indent_level).strip()]
354
355 if not for_statement._iteration_range[1]._index_type:
356 for_statement._iteration_range[1].hoist(True)
357 mlir_str = for_statement._iteration_range[1].print(self, indent_level).strip()
358 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
359 bounds_casting += ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
360 SyntaxTree.Node.context.push_block()
361 bounds_casting += indent + f"{mlir_id} = arith.index_cast {mlir_str} : {for_statement._iteration_range[1].get_type().print(self, indent_level).strip()} to index\n"
362 upbs = [mlir_id]
363 else:
364 upbs += [for_statement._iteration_range[1].print(self, indent_level).strip()]
365
366 bounds_casting += ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
367 mlir_id = f"%{SyntaxTree.Node.get_mlir_id()}"
368 bounds_casting += indent + f"{mlir_id} = arith.constant 1 : index\n"
369 steps = [mlir_id]
370
371 # Collect body
372 SyntaxTree.Node.context.push_block()
373 statement_prints = [statement.print(self, indent_level + 1) for statement in for_statement._statements]
374 statements = ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
375 statements += ''.join(statement_prints)
376
377 # Check if loop contains memory allocation (nested memref allocation must be sequential)
378 # Use scf.for for allocation loops, scf.parallel for computation loops
379 has_allocation = any(isinstance(stmt, SyntaxTree.MemoryAllocation) for stmt in for_statement._statements)
380
381 if has_allocation:
382 # Use sequential scf.for for memory allocation (cannot parallelize)
383 loop_header = (
384 f"{indent}scf.for {', '.join(iter_vars)} = "
385 f"{', '.join(lwbs)} to {', '.join(upbs)} step {', '.join(steps)} {{\n"
386 )
387 else:
388 # Use scf.parallel for computation loops (safe to parallelize)
389 loop_header = (
390 f"{indent}scf.parallel ({', '.join(iter_vars)}) = "
391 f"({', '.join(lwbs)}) to ({', '.join(upbs)}) step ({', '.join(steps)}) {{\n"
392 )
393 loop_footer = "\n" + indent + "}\n"
394
395 return bounds_casting + loop_header + statements + loop_footer
396
397 def print_comment(self, comment: SyntaxTree.Comment, indent_level=0):
398 return self.get_indent(indent_level) + "//" + comment._comment[1:]
399
400 def print_function_call(self, function_call: SyntaxTree.FunctionCall, indent_level=0):
401 indent = self.get_indent(indent_level)
402 SyntaxTree.Node.context.push_block()
403
404 # Detect if this is a bridge function call
405 is_bridge_call = function_call.id in ['sourceTerm', 'flux', 'nonconservativeProduct']
406
407 # Process arguments and handle array subscripts
408 argument_prints = []
409 for i, argument in enumerate(function_call._arguments):
410 argument.hoist(True)
411
412 # Check if this is a Reference to a 2D subscript for a bridge call
413 is_reference_to_2d_subscript = (
414 is_bridge_call and
415 isinstance(argument, SyntaxTree.Reference) and
416 isinstance(argument._expression, SyntaxTree.Subscript) and
417 isinstance(argument._expression._value, SyntaxTree.Subscript)
418 )
419
420 if is_reference_to_2d_subscript:
421 # This is Reference(Subscript[Subscript[2D_memref]])
422 # Extract the nested structure
423 outer_subscript = argument._expression # Subscript[Subscript[...], col_index]
424 inner_subscript = outer_subscript._value # Subscript[2D_memref, row_index]
425 memref_2d = inner_subscript._value
426
427 outer_subscript.hoist(True)
428 outer_mlir = outer_subscript.print(self, indent_level)
429 mlir_str_from_outer = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
430 SyntaxTree.Node.context.get_block().mlir_append(mlir_str_from_outer)
431
432 # Get MLIR representations
433 memref_2d.hoist(True)
434 memref_2d_mlir = memref_2d.print(self, indent_level).strip(' ')
435
436 # Get the base memref variable name (strip % prefix)
437 base_mlir_name = memref_2d_mlir.lstrip('%')
438 is_nested_base = False
439 if base_mlir_name in SyntaxTree.Node.type_map and SyntaxTree._is_nested_memref(SyntaxTree.Node.type_map[base_mlir_name]):
440 is_nested_base = True
441
442 # Use unified method from SyntaxTree.Node to generate pointer arithmetic
443 ptr_code, final_ptr = SyntaxTree.Node.generate_pointer_to_2d_element(
444 memref_2d_mlir, inner_subscript._new_index, outer_subscript._new_index, is_nested_base, indent_level
445 )
446 SyntaxTree.Node.context.get_block().mlir_append(ptr_code)
447 arg_mlir = final_ptr
448 else:
449 # Not a 2D subscript for bridge call - use standard approach
450 arg_mlir = argument.print(self, indent_level).strip(' ')
451
452 # Regular subscript (array indexing) that needs loading
453 if not is_reference_to_2d_subscript and isinstance(argument, SyntaxTree.Subscript):
454 # Get the base array and index
455 base_value = arg_mlir # This is the array reference
456 index = argument._new_index # This is the converted index
457 base_type = argument._value.get_type()
458
459 if isinstance(base_type, SyntaxTree.TDataBlock):
460 # Check if this is a multi-dimensional (2D+) DataBlock
461 if isinstance(base_type._dimensions, int) and base_type._dimensions >= 2:
462 # This is a flat multi-dimensional memref (like memref<?x?xf64>)
463 # When passed to a function expecting memref<?xf64>, just pass it as-is
464 arg_mlir = f"{base_value}[{index}]"
465 else:
466 # Single-dimensional DataBlock
467 element_type_class, is_vector, mlir_element_type = base_type.get_array_element_info()
468
469 if is_vector:
470 # This is an array of vectors - generate a load to get the vector pointer as i64, then cast to !llvm.ptr
471 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
472 load_op = indent + f"{result_id} = memref.load {base_value}[{index}] {{name = \"{base_value.strip('%')}\"}} : memref<?x{mlir_element_type}>\n"
473 SyntaxTree.Node.context.get_block().mlir_append(load_op)
474 # Now cast the i64 to !llvm.ptr
475 ptr_id = f"%{SyntaxTree.Node.get_mlir_id()}"
476 cast_op = indent + f"{ptr_id} = llvm.inttoptr {result_id} : {mlir_element_type} to !llvm.ptr\n"
477 SyntaxTree.Node.context.get_block().mlir_append(cast_op)
478 arg_mlir = ptr_id
479 else:
480 # Regular array of scalars - generate a load to get the scalar value
481 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
482 load_op = indent + f"{result_id} = memref.load {base_value}[{index}] {{name = \"{base_value.strip('%')}\"}} : memref<?x{mlir_element_type}>\n"
483 SyntaxTree.Node.context.get_block().mlir_append(load_op)
484 arg_mlir = result_id
485 else:
486 # Handle non-SyntaxTree.TDataBlock types (like nested memrefs)
487 if str(base_type).startswith("memref<?xmemref<?xf64>>"):
488 # This is an array of vectors (stored as memref of memrefs)
489 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
490 load_op = indent + f"{result_id} = memref.load {base_value}[{index}] {{name = \"{base_value.strip('%')}\"}} : memref<?xmemref<?xf64>>\n"
491 SyntaxTree.Node.context.get_block().mlir_append(load_op)
492 arg_mlir = result_id
493 else:
494 # For other types, just pass the reference
495 arg_mlir = f"{base_value}[{index}]"
496
497 argument_prints.append(arg_mlir)
498
499 argument_block = SyntaxTree.Node.context.pop_block().get_mlir()
500 for op in argument_block:
501 SyntaxTree.Node.context.get_block().mlir_append(' ' * indent_level * SyntaxTree.Node.spaces_per_tab + op.strip(' '))
502
503 # NOTE: we lookup the function pointer name in type_map to get the type
504 id = function_call.id[0].upper() + function_call.id[1:]
505 type_entry = SyntaxTree.Node.type_map[function_call.id]
506 # Handle both Type objects and strings
507 if isinstance(type_entry, str):
508 fn_type = type_entry
509 else:
510 fn_type = type_entry.print(self).strip()
511
512 bridge_function_name = f"{function_call.id}_bridge"
513 call_statement = f"""func.call @{bridge_function_name}({", ".join(argument_prints)}) : {fn_type.strip(' ')}"""
514
515 SyntaxTree.Node.context.get_block().mlir_append(indent + call_statement)
516 return ''
517
518 def print_max(self, max: SyntaxTree.Max, indent_level=0):
519 indent = self.get_indent(indent_level)
520
521 def unpack_nested_memref_access(expr, indent_level):
522 indent = ' ' * indent_level * SyntaxTree.Node.spaces_per_tab
523
524 if not (isinstance(expr, SyntaxTree.Subscript) and isinstance(expr._value, SyntaxTree.Subscript)):
525 return expr.print(self, indent_level).strip(' ')
526
527 outer_subscript = expr._value
528
529 if not hasattr(outer_subscript, '_new_index'):
530 outer_subscript.print(self, indent_level)
531 if not hasattr(expr, '_new_index'):
532 expr.print(self, indent_level)
533
534 outer_index = getattr(outer_subscript, '_new_index', outer_subscript._index.print(self, indent_level))
535 inner_index = getattr(expr, '_new_index', expr._index.print(self, indent_level))
536
537 inner_memref_id = f"%{SyntaxTree.Node.get_mlir_id()}"
538
539 # Get the outer memref's MLIR variable name and check type_map
540 outer_memref_var = outer_subscript._value.print(self, indent_level).strip(' ')
541
542 # Try to determine the outer memref type
543 # First, check if this variable is registered in type_map
544 try:
545 # Get the cpp name to check type_map
546 outer_var_cpp = outer_subscript._value.print(self.cpp_printer).strip()
547 if outer_var_cpp in SyntaxTree.Node.type_map:
548 type_entry = SyntaxTree.Node.type_map[outer_var_cpp]
549 # Handle both Type objects and strings
550 if isinstance(type_entry, str):
551 outer_memref_type = type_entry
552 else:
553 # Type object - call print(self)
554 outer_memref_type = type_entry.print(self).strip()
555 else:
556 # Fallback to default nested memref type
557 outer_memref_type = "memref<?xmemref<?xf64>>"
558 except:
559 outer_memref_type = "memref<?xmemref<?xf64>>"
560
561 mlir_str = indent + f"{inner_memref_id} = memref.load {outer_memref_var}[{outer_index}] {{name = \"{outer_memref_var.strip('%')}\"}} : {outer_memref_type}\n"
562 value_id = f"%{SyntaxTree.Node.get_mlir_id()}"
563 mlir_str += indent + f"{value_id} = memref.load {inner_memref_id}[{inner_index}] {{name = \"{outer_memref_var.strip('%')}\"}} : memref<?xf64>\n"
564 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
565
566 return value_id
567
568 SyntaxTree.Node.context.push_block()
569
570 if type(max._lhs.get_type()) is SyntaxTree.TDataBlock:
571 max._lhs.hoist(True)
572 if type(max._rhs.get_type()) is SyntaxTree.TDataBlock:
573 max._rhs.hoist(True)
574
575 const_zero_id = None
576 if isinstance(max._lhs, SyntaxTree.Subscript) and isinstance(max._lhs._value, SyntaxTree.Subscript):
577 lhs = unpack_nested_memref_access(max._lhs, indent_level)
578 else:
579 lhs = max._lhs.print(self, indent_level).strip(' ')
580 if SyntaxTree.TDataBlock.needs_memref_load(max._lhs):
581 const_zero_id = SyntaxTree.TDataBlock.ensure_const_zero(const_zero_id, indent_level)
582 lhs = SyntaxTree.TDataBlock.generate_memref_load(max._lhs, lhs, max._lhs._index.print(self), indent_level)
583
584 if isinstance(max._rhs, SyntaxTree.Subscript) and isinstance(max._rhs._value, SyntaxTree.Subscript):
585 rhs = unpack_nested_memref_access(max._rhs, indent_level)
586 else:
587 rhs = max._rhs.print(self, indent_level).strip(' ')
588 if SyntaxTree.TDataBlock.needs_memref_load(max._rhs):
589 const_zero_id = SyntaxTree.TDataBlock.ensure_const_zero(const_zero_id, indent_level)
590 rhs = SyntaxTree.TDataBlock.generate_memref_load(max._rhs, rhs, const_zero_id, indent_level)
591
592 argument_block = SyntaxTree.Node.context.pop_block().get_mlir()
593 for op in argument_block:
594 SyntaxTree.Node.context.get_block().mlir_append(op)
595
596 # Use the unified type determination and operation mapping from SyntaxTree.TDataBlock
597 op_type_class = SyntaxTree.TDataBlock.get_mlir_type_for_operation(max._lhs, max._rhs)
598 result_type = op_type_class().print(self)
599
600 if op_type_class is SyntaxTree.TInteger:
601 mlir_op = "arith.maxsi"
602 else:
603 mlir_op = "arith.maximumf" # Default to float
604
605 new_id = f"%{SyntaxTree.Node.get_mlir_id()}"
606 mlir_str = indent + f"{new_id} = {mlir_op} {lhs}, {rhs} : {result_type}\n"
607 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
608
609 return new_id
610
611 def print_memory_allocation(self, memory_allocation: SyntaxTree.MemoryAllocation, indent_level=0):
612 indent = self.get_indent(indent_level)
613
614 # Comprehensive tracing for MemoryAllocation
615 name_mlir = memory_allocation._name.print(self)
616 name_type = memory_allocation._name.get_type()
617 is_datablock = isinstance(name_type, SyntaxTree.TDataBlock)
618 var_name_for_map = memory_allocation._name.print(self.cpp_printer).strip()
619 in_type_map = var_name_for_map in SyntaxTree.Node.type_map
620
621 if len(memory_allocation._dimensions) == 1:
622 # Process the dimension expression first and collect any intermediate operations
623 SyntaxTree.Node.context.push_block()
624 index_val = memory_allocation._dimensions[0].print(self, indent_level)
625 intermediate_ops = SyntaxTree.Node.context.pop_block().get_mlir()
626
627 # Now build the allocation operations
628 index_id = f"%{SyntaxTree.Node.get_mlir_id()}"
629 decl = ''.join(intermediate_ops)
630
631 # Add the index cast and allocation with correct source type
632 index_type = memory_allocation._dimensions[0].get_type()
633 index_mlir_type = index_type.print(self).strip()
634 decl += indent + f"{index_id} = arith.index_cast {index_val} : {index_mlir_type} to index\n"
635 new_id = f"%{SyntaxTree.Node.get_mlir_id()}"
636
637 # For 1D allocation, we should always generate 1D memref type
638 # Get the element type correctly
639 name_type = memory_allocation._name.get_type()
640 if isinstance(name_type, SyntaxTree.TDataBlock) or isinstance(name_type, SyntaxTree.TDiagonalMatrix) or isinstance(name_type, SyntaxTree.TMatrix):
641 # Get the element type of the DataBlock
642 element_type_class = name_type.get_element_type()
643 element_type = element_type_class().print(self)
644 mlir_type = f"memref<?x{element_type}>"
645 else:
646 mlir_type = name_type.print(self)
647
648 if mlir_type is None and SyntaxTree.Node.type_map[var_name_for_map] == "double*":
649 mlir_type = "memref<?xf64>"
650 matrix_names = ["identity_matrix", "mass_matrix_2d"]
651 if name_mlir.strip("%") in matrix_names:
652 new_id = "%" + name_mlir.strip("%")
653 SyntaxTree.MemoryAllocation.allocated_types[memory_allocation._name.print(self)] = mlir_type
654 decl += indent + f"{new_id} = memref.alloc({index_id}) {{name = \"{name_mlir.strip('%')}\"}} : {mlir_type}\n"
655 # Track the allocated type for deallocation
656 SyntaxTree.MemoryAllocation.allocated_types[new_id] = mlir_type
657
658 # Check if this is an inner allocation that needs to be stored in an outer array
659 # This happens when memory_allocation._name is a Subscript (indicating arr[i] = allocated_inner_array)
660 if isinstance(memory_allocation._name, SyntaxTree.Subscript) and not memory_allocation._specify_type:
661 # This is an inner allocation for nested memref, add the store operation
662 # Get the outer array name and index
663 outer_array = memory_allocation._name._value # This should be the outer array name
664 outer_index = memory_allocation._name._index
665
666 # Process the outer array and index expressions to collect any additional operations
667 SyntaxTree.Node.context.push_block()
668 outer_array_mlir = outer_array.print(self, indent_level)
669 outer_index_mlir = outer_index.print(self, indent_level)
670 additional_ops = SyntaxTree.Node.context.pop_block().get_mlir()
671
672 # Add any additional operations
673 decl += ''.join(additional_ops)
674
675 # When storing a memref into a nested memref array, we need to use memref<?xmemref<?xf64>>
676 type_mlir = SyntaxTree.Node.get_mlir_type(memory_allocation._type)
677 if type_mlir is None:
678 type_entry = SyntaxTree.Node.type_map[memory_allocation._type.print(self.cpp_printer)]
679 # Handle both Type objects and strings
680 if isinstance(type_entry, str):
681 type_mlir = type_entry.strip(' ')
682 else:
683 # Type object - call print(self)
684 type_mlir = type_entry.print(self).strip()
685 outer_type = f"memref<?xmemref<?x{type_mlir}>>"
686
687 # Add the store operation
688 decl += indent + f"memref.store {new_id}, {outer_array_mlir}[{outer_index_mlir}] {{name = \"{outer_array_mlir.strip('%')}\"}} : {outer_type}"
689 return decl
690 else:
691 # For multi-dimensional arrays, check type_map first (ordering is important)
692 # This allows overriding default DataBlock behavior for nested memref types
693 var_name_for_type_check = memory_allocation._name.print(self.cpp_printer).strip()
694 in_type_map = var_name_for_type_check in SyntaxTree.Node.type_map
695
696 # Use nested memref if in type_map, otherwise check if DataBlock flat
697 if in_type_map:
698 # Variable has explicit type specification - use nested memref path
699 is_datablock = False # Force nested path
700 actual_dims_calc = -1
701 else:
702 # For multi-dimensional arrays, check if this is a DataBlock
703 # If so, use flat memref with the correct number of dimensions instead of nested memref
704 is_datablock = isinstance(memory_allocation._name.get_type(), SyntaxTree.TDataBlock)
705
706 # Calculate actual_dims based on DataBlock iteration_range
707 if is_datablock:
708 datablock_obj = memory_allocation._name.get_type()
709
710 if hasattr(datablock_obj, '_iteration_range'):
711 actual_dims_calc = len(datablock_obj._iteration_range)
712 elif isinstance(datablock_obj._dimensions, int):
713 actual_dims_calc = 1 if datablock_obj._dimensions == 1 else 2
714 elif isinstance(datablock_obj._dimensions, list):
715 actual_dims_calc = len(datablock_obj._dimensions) if len(datablock_obj._dimensions) > 0 else 1
716 else:
717 actual_dims_calc = 1
718 else:
719 actual_dims_calc = -1 # Not a DataBlock
720
721 if is_datablock and actual_dims_calc >= 2:
722 # Multi-dimensional DataBlock case: allocate as flat N-D memref
723 element_type_class = memory_allocation._name.get_type().get_element_type()
724 element_type = element_type_class().print(self)
725
726 # We need actual_dims_calc dimensions from memory_allocation._dimensions
727 # For a 2D array: _dimensions might have 2, 3, or 4 elements (depends on iteration_range structure)
728 # Take the first actual_dims_calc elements
729 dim_exprs = memory_allocation._dimensions[:actual_dims_calc]
730 if len(dim_exprs) < actual_dims_calc:
731 # Fallback: if we don't have enough dimensions, use what we have
732 dim_exprs = memory_allocation._dimensions
733
734 intermediate_decl = ""
735 dim_ids = []
736
737 for i, dim_expr in enumerate(dim_exprs):
738 # Evaluate the dimension expression - this appends its operations to context
739 dim_val = dim_expr.print(self, indent_level)
740 dim_id = f"%{SyntaxTree.Node.get_mlir_id()}"
741 dim_ids.append(dim_id)
742 dim_type = dim_expr.get_type()
743 dim_mlir_type = dim_type.print(self).strip()
744
745 # Generate the index cast (will be appended after dimension expressions)
746 intermediate_decl += indent + f"{dim_id} = arith.index_cast {dim_val} : {dim_mlir_type} to index\n"
747
748 # These must come before the index_cast operations
749 dim_expr_ops = SyntaxTree.Node.context.pop_block().get_mlir()
750 intermediate_decl = ''.join(dim_expr_ops) + intermediate_decl
751
752 # Re-push a new block for remaining operations
753 SyntaxTree.Node.context.push_block()
754
755 # Process name and allocate
756 name_mlir = memory_allocation._name.print(self, indent_level)
757
758 # Build memref type: memref<?x?x...xf64> with actual_dims_calc '?'s
759 dimension_list = ["?"] * actual_dims_calc
760 memref_dims = "x".join(dimension_list)
761 alloc_args = ", ".join(dim_ids)
762
763 # Allocate as flat N-D memref
764 alloc_line = indent + f"{name_mlir} = memref.alloc({alloc_args}) {{name = \"{name_mlir.strip('%')}\"}} : memref<{memref_dims}x{element_type}>\n"
765
766 # Track the allocated memref type so it can be used in load/store operations
767 memref_type_full = f"memref<{memref_dims}x{element_type}>"
768 SyntaxTree.Node.mlir_memref_types[name_mlir] = memref_type_full
769 SyntaxTree.MemoryAllocation.allocated_types[name_mlir] = memref_type_full
770
771 return intermediate_decl + alloc_line
772 else:
773 # For non-DataBlock multi-dimensional arrays, use nested memref with loop
774 # Use the existing loop structure created in the constructor
775 size = memory_allocation._dimensions[-2]
776 for i in range(len(memory_allocation._dimensions) - 3, -1, -1):
777 size = size * memory_allocation._dimensions[i]
778
779 # Process the outer dimension expression first and collect any intermediate operations
780 SyntaxTree.Node.context.push_block()
781 outer_size_val = memory_allocation._dimensions[-1].print(self, indent_level)
782 intermediate_ops = SyntaxTree.Node.context.pop_block().get_mlir()
783
784 # Build the allocation operations
785 decl = ''.join(intermediate_ops)
786
787 # Add the index cast and allocation with correct source type
788 outer_size_id = f"%{SyntaxTree.Node.get_mlir_id()}"
789 outer_size_type = memory_allocation._dimensions[-1].get_type()
790 outer_size_mlir_type = outer_size_type.print(self).strip()
791 decl += indent + f"{outer_size_id} = arith.index_cast {outer_size_val} : {outer_size_mlir_type} to index\n"
792
793 # Determine the correct type for the outer array
794 # Check if this variable name is in type_map (e.g., maxEigenvaluesX, maxEigenvaluesY)
795 var_name = memory_allocation._name.print(self.cpp_printer).strip()
796
797 # NOTE: We need to check if the variable name is in type_map first
798 if var_name in SyntaxTree.Node.type_map:
799 # Use the explicitly registered type from type_map
800 type_entry = SyntaxTree.Node.type_map[var_name]
801
802 # Handle both Type objects and strings
803 if isinstance(type_entry, str):
804 outer_type = type_entry
805 else:
806 # Type object (e.g., TNestedMemref)
807 outer_type = type_entry.print(self).strip()
808 elif isinstance(memory_allocation._name.get_type(), SyntaxTree.TDataBlock):
809 # Get the element type correctly for the inner memref
810 element_type_class = memory_allocation._name.get_type().get_element_type()
811 element_type = element_type_class().print(self)
812 outer_type = f"memref<?xmemref<?x{element_type}>>"
813 else:
814 # Fallback for non-DataBlock types
815 name_type_mlir = memory_allocation._name.get_type().print(self)
816
817 if name_type_mlir is None:
818 type_entry = SyntaxTree.Node.type_map[memory_allocation._name.get_type().print(self.cpp_printer)]
819 # Handle both Type objects and strings
820 if isinstance(type_entry, str):
821 name_type_mlir = type_entry.strip(' ')
822 else:
823 # Type object
824 name_type_mlir = type_entry.print(self).strip()
825 outer_type = f"memref<?x{name_type_mlir}>"
826
827 # Process the name expression to collect any additional operations
828 SyntaxTree.Node.context.push_block()
829 name_mlir = memory_allocation._name.print(self, indent_level)
830 name_ops = SyntaxTree.Node.context.pop_block().get_mlir()
831
832 # Add any name-related operations
833 decl += ''.join(name_ops)
834 decl += indent + f"{name_mlir} = memref.alloc({outer_size_id}) {{name = \"{name_mlir.strip('%')}\"}} : {outer_type}\n"
835 # Track the allocated type for deallocation
836 SyntaxTree.MemoryAllocation.allocated_types[name_mlir] = outer_type
837
838 # Use the existing loop structure for inner allocations
839 loop_mlir = memory_allocation._loop.print(self, indent_level)
840
841 return decl + loop_mlir
842
843 def print_memory_deallocation(self, memory_deallocation: SyntaxTree.MemoryDeallocation, indent_level=0):
844 indent = self.get_indent(indent_level)
845
846 # Get the MLIR variable name
847 name_mlir = memory_deallocation._name.print(self, indent_level)
848 # Use the stored allocated type if available
849 if name_mlir in SyntaxTree.MemoryAllocation.allocated_types:
850 name_type_mlir = SyntaxTree.MemoryAllocation.allocated_types[name_mlir]
851 elif memory_deallocation._allocated_type is not None:
852 # Use the allocated type if it was set explicitly
853 name_type_mlir = memory_deallocation._allocated_type
854 else:
855 # Fallback: try to reconstruct the type from the allocation dimensions
856 # For nested memrefs (multi-dimensional arrays), we need memref<?xmemref<?xf64>>
857 # For 1D arrays, we need memref<?xf64>
858 name_element_type = memory_deallocation._name.get_type()
859 element_type = name_element_type.print(self).strip()
860
861 if len(memory_deallocation._dimensions) == 1:
862 name_type_mlir = f"memref<?x{element_type}>"
863 else:
864 # For multidimensional, assume nested memref
865 name_type_mlir = f"memref<?xmemref<?x{element_type}>>"
866
867 if name_mlir[0] != "%":
868 name_mlir = "%" + name_mlir
869 return "\n" + indent + f"""memref.dealloc {name_mlir} : {name_type_mlir}\n"""
870
871 def print_assignment(self, assignment: SyntaxTree.Assignment, indent_level = 0):
872 indent = self.get_indent(indent_level)
873 mlir_str = ''
874 SyntaxTree.Node.context.push_block()
875 rhs = assignment._rhs.print(self, indent_level)
876
877 rhs_indices = []
878 rhs_dims = []
879 rhs_casting_mlir = ''
880
881 if isinstance(assignment._rhs, SyntaxTree.Subscript):
882 mlir_list = SyntaxTree.Node.context.get_block().get_mlir()
883 for i in range(assignment._rhs._dimensions):
884 rhs_dims.append('?')
885 if len(mlir_list) > 0:
886 rhs_indices.insert(0, assignment._rhs._new_index)
887 rhs_casting_mlir += mlir_list.pop()
888
889 mlir_str += ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
890 mlir_str += rhs_casting_mlir
891 else:
892 # Assume scalar here
893 tmp = ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
894 mlir_str += tmp
895
896 SyntaxTree.Node.context.push_block()
897 lhs = assignment._lhs.print(self, indent_level)
898
899 lhs_indices = []
900 lhs_dims = []
901 lhs_casting_mlir = ''
902
903 if isinstance(assignment._lhs, SyntaxTree.Subscript):
904 mlir_list = SyntaxTree.Node.context.get_block().get_mlir()
905 for i in range(assignment._lhs._dimensions):
906 lhs_dims.append('?')
907 if len(mlir_list) > 0:
908 popped = mlir_list.pop()
909 lhs_indices.insert(0, assignment._lhs._new_index)
910 lhs_casting_mlir += popped
911
912 mlir_str += ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
913 mlir_str += lhs_casting_mlir
914 else:
915 tmp = ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
916 mlir_str += tmp
917
918 # Check if LHS is a flat 2D memref (Subscript[Subscript] on String-based field)
919 lhs_is_flat_2d = False
920 lhs_memref_name = None
921 all_lhs_indices = []
922
923 if isinstance(assignment._lhs, SyntaxTree.Subscript) and isinstance(assignment._lhs._value, SyntaxTree.Subscript):
924 # Check if the base is a String-based field
925 base_base = assignment._lhs._value._value
926 if isinstance(base_base, SyntaxTree.DataBlock) and isinstance(base_base._internal, SyntaxTree.String):
927 # This is a String-based field which is now a flat 2D memref
928 lhs_is_flat_2d = True
929 dsl_name = base_base._internal._value
930 lhs_memref_name = SyntaxTree.Node.mlir_symbol_table[dsl_name]
931
932 outer_sub = assignment._lhs
933 inner_sub = assignment._lhs._value
934
935 if hasattr(inner_sub, '_new_index'):
936 all_lhs_indices.append(inner_sub._new_index)
937 if hasattr(outer_sub, '_new_index'):
938 all_lhs_indices.append(outer_sub._new_index)
939
940 if lhs_is_flat_2d:
941 rhs_value = rhs
942
943 lhs_index_str = '[' + ','.join(all_lhs_indices) + ']'
944 lhs_type = f"memref<?x?xf64>"
945 mlir_str += indent + f"memref.store {rhs_value}, {lhs_memref_name}{lhs_index_str} {{name = \"{lhs_memref_name.strip('%')}\"}} : {lhs_type}\n"
946 elif isinstance(assignment._lhs, SyntaxTree.Subscript) and isinstance(assignment._lhs._value, SyntaxTree.Subscript):
947 base_base = assignment._lhs._value._value
948
949 # If base is not a String field, it's a true nested memref (e.g. lambdaLeft[i][j])
950 if not (isinstance(base_base, SyntaxTree.DataBlock) and isinstance(base_base._internal, SyntaxTree.String)):
951 # This is a nested memref store (e.g., lambdaLeft[i][j] = value)
952 rhs_value = rhs
953 if isinstance(assignment._rhs, SyntaxTree.Float):
954 const_id = f"%{SyntaxTree.Node.get_mlir_id()}"
955 mlir_str += indent + f"{const_id} = arith.constant {assignment._rhs._value} : f64\n"
956 rhs_value = const_id
957 elif isinstance(assignment._rhs, SyntaxTree.Integer):
958 const_id = f"%{SyntaxTree.Node.get_mlir_id()}"
959 mlir_str += indent + f"{const_id} = arith.constant {assignment._rhs._value} : i32\n"
960 rhs_value = const_id
961 elif isinstance(assignment._rhs, SyntaxTree.Subscript):
962 rhs_id = f"%{SyntaxTree.Node.get_mlir_id()}"
963 rhs_index_str = '[' + ','.join(rhs_indices) + ']'
964 rhs_type = "memref<?xf64>" if not rhs_dims else f"memref<{ 'x'.join(rhs_dims) }xf64>"
965 mlir_str += indent + f"{rhs_id} = memref.load {rhs}{rhs_index_str} {{name = \"{rhs.strip('%')}\"}} : {rhs_type.strip(' ')}\n"
966 rhs_value = rhs_id
967
968 # Extract indices from nested subscript chain
969 # lhs._value is the inner subscript, lhs is the outer subscript
970 outer_index = assignment._lhs._new_index
971 inner_memref_var = assignment._lhs._value._value.print(self, indent_level) # Base memref name
972
973 # Load the inner memref from the outer nested structure
974 inner_memref_id = f"%{SyntaxTree.Node.get_mlir_id()}"
975 # Get the actual outer memref type from type_map
976 base_var = assignment._lhs._value._value
977 type_entry = SyntaxTree.Node.type_map[base_var.id]
978
979 if isinstance(type_entry, str):
980 outer_memref_type = type_entry
981 else:
982 outer_memref_type = type_entry.print(self).strip()
983
984 # Load inner memref with first index
985 inner_sub_index = assignment._lhs._value._new_index
986 mlir_str += indent + f"{inner_memref_id} = memref.load {inner_memref_var}[{inner_sub_index}] {{name = \"{inner_memref_var.strip('%')}\"}} : {outer_memref_type}\n"
987
988 # Store directly into the inner memref with the second index
989 inner_memref_type = "memref<?xf64>" # Default inner arrays are 1D
990 mlir_str += indent + f"memref.store {rhs_value}, {inner_memref_id}[{outer_index}] {{name = \"{inner_memref_var.strip('%')}\"}} : {inner_memref_type}\n"
991 else:
992 # Handle regular flat memref assignment (non-nested)
993 rhs_id = f"%{SyntaxTree.Node.get_mlir_id()}"
994
995 lhs_index_str = '[' + ','.join(lhs_indices) + ']'
996 # Ensure at least one dimension for memref type
997 if lhs_dims:
998 lhs_type = f"memref<{ 'x'.join(lhs_dims) }xf64>"
999 else:
1000 lhs_type = "memref<?xf64>"
1001 rhs_index_str = '[' + ','.join(rhs_indices) + ']'
1002 if rhs_dims:
1003 rhs_type = f"memref<{ 'x'.join(rhs_dims) }x f64>"
1004 else:
1005 rhs_type = "memref<?xf64>"
1006
1007 if isinstance(assignment._rhs, SyntaxTree.Subscript):
1008 mlir_str += indent + f"{rhs_id} = memref.load {rhs}{rhs_index_str} {{name = \"{rhs.strip('%')}\"}} : {rhs_type.strip(' ')}\n"
1009 rhs_value = rhs_id
1010 else:
1011 rhs_value = rhs
1012
1013 if isinstance(assignment._rhs, SyntaxTree.Float):
1014 const_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1015 mlir_str += indent + f"{const_id} = arith.constant {assignment._rhs._value} : f64\n"
1016 rhs_value = const_id
1017 elif isinstance(assignment._rhs, SyntaxTree.Integer):
1018 const_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1019 mlir_str += indent + f"{const_id} = arith.constant {assignment._rhs._value} : i32\n"
1020 rhs_value = const_id
1021
1022 if isinstance(assignment._lhs, SyntaxTree.Subscript):
1023 mlir_str += indent + f"memref.store {rhs_value}, {lhs}{lhs_index_str} {{name = \"{lhs.strip('%')}\"}} : {lhs_type.strip(' ')}\n"
1024 else:
1025 lhs_type_obj = assignment._lhs.get_type()
1026 lhs_mlir_type = lhs_type_obj.print(self, indent_level).strip(' ')
1027 mlir_str += indent + f"memref.store {rhs_value}, {lhs} {{name = \"{lhs.strip('%')}\"}} : {lhs_mlir_type}\n"
1028
1029 return mlir_str
1030
1031 def print_construction(self, construction: SyntaxTree.Construction, indent_level=0):
1032 indent = self.get_indent(indent_level)
1033
1034 if isinstance(construction._expression, SyntaxTree.Integer):
1035 if construction._expression._string is None:
1036 expr_mlir = construction._expression.print(self, indent_level)
1037
1038 # Get any operations that were added to the context
1039 mlir_str = ''.join(SyntaxTree.Node.context.get_block().pop_mlir())
1040 mlir_str += indent + f"{construction._name.print(self, indent_level)} = {expr_mlir} : {construction._name.get_type().print(self, indent_level).strip(' ')}\n"
1041 else:
1042 # Get another variable id and load the pointer into that, then load the value into the actual variable (construction)
1043 new_id = SyntaxTree.Node.get_mlir_id()
1044 mlir_str = indent + f"%{new_id} = {construction._expression.print(self, indent_level)}\n"
1045
1046 # Use the expression's type, not the name's type
1047 expr_type = construction._expression.get_type().print(self, indent_level).strip(' ')
1048 mlir_str += indent + f"{construction._name.print(self, indent_level)} = llvm.load %{new_id} {{name = \"{construction._name.print(self, indent_level).strip('%')}\"}} : !llvm.ptr -> {expr_type}\n"
1049 else:
1050 mlir_str = f"{self.get_indent(indent_level)}{construction._name.print(self)} = {construction._expression.print(self, indent_level)}\n"
1051 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1052 return ''
1053
1054 def print_data_block_construction_from_existing(self, construction: SyntaxTree.DataBlockConstructionFromExisting, indent_level=0):
1055 indent = self.get_indent(indent_level)
1056 #if isinstance(construction._name.get_type(), SyntaxTree.TDiagonalMatrix):
1057 if construction._name.id[-2:] == "1d":
1058 return f"""{self.get_indent(indent_level)}{construction._name.print(self)} = builtin.unrealized_conversion_cast %{construction._dataBlock._internal._value} : !llvm.struct<(ptr, ptr, i64, array<1 x i64>, array<1 x i64>)> to memref<?xf64> {{name = "{construction._name.print(self.cpp_printer)}"}} """
1059 #return f"{self.get_indent(indent_level)}{construction._name.print(self)} = %{construction._dataBlock._internal._value}"
1060 else:
1061 type_print = construction._name.get_type().print(self, indent_level)
1062
1063 dims = []
1064
1065 for i in range(len(construction._dataBlock._iteration_range) - 3, -1, -1):
1066 dims.append(construction._dataBlock._iteration_range[i])
1067
1068 if len(construction._dataBlock._iteration_range) > 1:
1069 type_print = "!llvm.ptr " #+ f"{construction._dataBlock._underlying_type} dims: {dims})"
1070
1071 # NOTE: We encode structs as Strings, so check here if they are a struct and generate accordingly
1072 # Set the target variable name so String.print(self) can use it
1073 target_var_name = construction._name.print(self, indent_level)
1074 SyntaxTree.Node.target_variable_name = target_var_name
1075
1076 # Store the mapping from DSL name to MLIR variable name for later use in assignments
1077 # TODO: This is clumsy - it is assuming the exact flow here...
1078 dsl_name = construction._dataBlock._internal._value
1079 SyntaxTree.Node.mlir_symbol_table[dsl_name] = target_var_name
1080
1081 # Generate the struct field access operations with the target variable name
1082 struct_mlir = construction._dataBlock._internal.print(self, indent_level).strip(' ')
1083 mlir_str = ''.join(SyntaxTree.Node.context.pop_block().get_mlir())
1084
1085 # Clear the target variable name - we've finished generating the MLIR for this SyntaxTree.Node
1086 SyntaxTree.Node.target_variable_name = None
1087
1088 return mlir_str
1089
1090 def print_data_block_construction_from_operation(self, construction: SyntaxTree.DataBlockConstructionFromOperation, indent_level=0):
1091 return f"""{construction.memoryAllocation.print(self, indent_level)}
1092{construction.assignment.print(self, indent_level)}"""
1093
1094 def print_data_block_construction_from_function(self, construction: SyntaxTree.DataBlockConstructionFromFunction, indent_level=0):
1095 SyntaxTree.Node.context.push_block()
1096 mem_alloc = construction.memoryAllocation.print(self, indent_level)
1097 init = construction.initialisation.print(self, indent_level)
1098 alloc_block = SyntaxTree.Node.context.pop_block().get_mlir()
1099 for op in alloc_block:
1100 SyntaxTree.Node.context.get_block().mlir_append(op)
1101
1102 return f"""{mem_alloc}
1103{init}"""
1104
1105 def print_data_block_construction_from_matrix_vector_product(self, construction: SyntaxTree.DataBlockConstructionFromFunction, indent_level=0):
1106 return f"""{construction.memoryAllocation.print(self, indent_level)}
1107{construction.assignment.print(self, indent_level)}"""
1108
1109 def print_data_block_construction_from_dot_product(self, construction: SyntaxTree.DataBlockConstructionFromFunction, indent_level=0):
1110 return f"""{construction.memoryAllocation.print(self, indent_level)}
1111{construction.assignment.print(self, indent_level)}"""
1112
1113 def print_diagonal_matrix(self, matrix: SyntaxTree.DiagonalMatrix, indent_level=0):
1114 if matrix._id is None:
1115 return matrix._internal.print(self, indent_level)
1116 else:
1117 print(matrix._id)
1118 return "%" + str(matrix._id)
1119
1120 def print_matrix(self, construction: SyntaxTree.Matrix, indent_level=0):
1121 if construction.id is None:
1122 return construction._internal.print(self, indent_level)
1123 else:
1124 return "%" + construction.id
1125
1126 def print_diagonal_matrix_construction_from_kronecker_product(self, construction: SyntaxTree.DataBlockConstructionFromFunction, indent_level=0):
1127 SyntaxTree.Node.type_map.update({construction._matrix.print(self.cpp_printer): SyntaxTree.TDiagonalMatrix().print(self.cpp_printer)})
1128 return f"""{construction._memory_allocation.print(self, indent_level)}
1129{construction._assignment.print(self, indent_level)}"""
1130
1131 def print_diagonal_matrix_construction_from_value(self, construction: SyntaxTree.DataBlockConstructionFromFunction, indent_level=0):
1132 return f"""{construction._memory_allocation.print(self, indent_level)}
1133{construction._assignment.print(self, indent_level)}"""
1134
1135 def print_matrix_construction_from_kronecker_product(self, construction: SyntaxTree.DataBlockConstructionFromFunction, indent_level=0):
1136 return f"""{construction._memory_allocation.print(self, indent_level)}
1137{construction._initialisation.print(self, indent_level)}
1138{construction._assignment.print(self, indent_level)}"""
1139
1140 def print_data_block_assignment(self, assignment: SyntaxTree.DataBlockAssignment, indent_level=0):
1141 return assignment._loop.print(self, indent_level)
1142
1143 def print_boolean(self, boolean: SyntaxTree.Boolean, indent_level = 0):
1144 # NOTE: Literals are returned and used in-line, no hoisting required
1145 return str(boolean._value)
1146
1147 def print_string(self, string: SyntaxTree.String, indent_level = 0):
1148 indent = self.get_indent(indent_level)
1149
1150 match string._value:
1151 case x if '.' in x:
1152 struct, field = x.split('.')
1153 if struct in SyntaxTree.Node.struct_map:
1154 struct_list = SyntaxTree.Node.struct_map[struct]
1155 idx = [item[0] for item in struct_list].index(field)
1156 mlir_type_target, mlir_type_struct = struct_list[idx][1].split('|||')
1157
1158 # Generate the MLIR operations
1159 struct_val_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1160
1161 # TODO: Special case: maxEigenvalue is also a function parameter, so we have to hack it here
1162 if field == "maxEigenvalue":
1163 field_id = f"%{field}s"
1164 else:
1165 field_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1166
1167 # Build the basic MLIR operations
1168 mlir_str = indent + f"{struct_val_id} = llvm.load %{struct} {{name = \"{struct.strip('%')}\"}}: !llvm.ptr -> {mlir_type_struct}\n"
1169
1170 # If the extracted field is a raw pointer (not a descriptor struct),
1171 # extract it directly with a _ptr suffix to distinguish from the final memref
1172 if not mlir_type_target.startswith("!llvm.struct<"):
1173 # For raw pointers (like maxEigenvalue field [11]), use _ptr naming
1174 ptr_field_id = f"%{field}{'_ptr' if field != 'maxEigenvalue' else '_ptr'}"
1175 mlir_str += indent + f"{ptr_field_id} = llvm.extractvalue {struct_val_id}[{idx}] : {mlir_type_struct}\n"
1176 field_id = ptr_field_id
1177 else:
1178 # For descriptor structs, extract normally (will be processed below)
1179 mlir_str += indent + f"{field_id} = llvm.extractvalue {struct_val_id}[{idx}] : {mlir_type_struct}\n"
1180
1181 final_id = field_id
1182
1183 # If the extracted field is a descriptor struct, convert it to appropriate memref
1184 if mlir_type_target.startswith("!llvm.struct<"):
1185 # Use getelementptr[0, field_idx] to properly access struct fields
1186 ptr_getelementptr_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1187 ptr_load_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1188 mlir_str += indent + f"{ptr_getelementptr_id} = llvm.getelementptr %{struct}[0, {idx}] : (!llvm.ptr) -> !llvm.ptr, {mlir_type_struct}\n"
1189 mlir_str += indent + f"{ptr_load_id} = llvm.load {ptr_getelementptr_id} {{name = \"{struct.strip('%')}\"}}: !llvm.ptr -> {mlir_type_target}\n"
1190
1191 # Extract all 5 descriptor fields
1192 alloc_ptr_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1193 aligned_ptr_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1194 offset_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1195 sizes_0_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1196 strides_0_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1197
1198 mlir_str += indent + f"{alloc_ptr_id} = llvm.extractvalue {ptr_load_id}[0] : {mlir_type_target}\n"
1199 mlir_str += indent + f"{aligned_ptr_id} = llvm.extractvalue {ptr_load_id}[1] : {mlir_type_target}\n"
1200 mlir_str += indent + f"{offset_id} = llvm.extractvalue {ptr_load_id}[2] : {mlir_type_target}\n"
1201 mlir_str += indent + f"{sizes_0_id} = llvm.extractvalue {ptr_load_id}[3, 0] : {mlir_type_target}\n"
1202 mlir_str += indent + f"{strides_0_id} = llvm.extractvalue {ptr_load_id}[4, 0] : {mlir_type_target}\n"
1203
1204 # Determine target memref type based on descriptor dimensions
1205 is_2d = "array<2 x i64>" in mlir_type_target
1206
1207 # For 2D descriptors, also extract sizes[1] and strides[1]
1208 sizes_1_id = None
1209 strides_1_id = None
1210
1211 if is_2d:
1212 sizes_1_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1213 strides_1_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1214 mlir_str += indent + f"{sizes_1_id} = llvm.extractvalue {ptr_load_id}[3, 1] : {mlir_type_target}\n"
1215 mlir_str += indent + f"{strides_1_id} = llvm.extractvalue {ptr_load_id}[4, 1] : {mlir_type_target}\n"
1216
1217 # Use type_map to get the correct memref type for this field
1218 # For fields like cellCentre, cellSize, t, dt, we need to use their correct types from type_map
1219 if field in SyntaxTree.Node.type_map:
1220 type_entry = SyntaxTree.Node.type_map[field]
1221 # Handle both Type objects and strings
1222 if isinstance(type_entry, str):
1223 target_memref_type = type_entry
1224 else:
1225 # Type object - call print(self)
1226 target_memref_type = type_entry.print(self).strip()
1227 else:
1228 # Fallback to descriptor-based inference
1229 if is_2d:
1230 # 2D descriptor
1231 target_memref_type = "memref<?x?xf64>"
1232 else:
1233 # 1D descriptor (assume array<1 x i64>)
1234 target_memref_type = "memref<?xf64>"
1235
1236 # Create memref descriptor from the extracted fields using the new method
1237 conversion_mlir, memref_id = SyntaxTree.Node.create_memref_from_extracted_descriptor(
1238 alloc_ptr_id,
1239 aligned_ptr_id,
1240 offset_id,
1241 sizes_0_id,
1242 sizes_1_id,
1243 strides_0_id,
1244 strides_1_id,
1245 target_memref_type,
1246 mlir_type_target,
1247 indent_level,
1248 target_var_name=SyntaxTree.Node.target_variable_name,
1249 is_2d=is_2d)
1250 mlir_str += conversion_mlir
1251 final_id = memref_id
1252 elif 'memref' in mlir_type_target or mlir_type_target == '!llvm.ptr':
1253 # For explicit memref types or raw pointers (like maxEigenvalue),
1254 # use create_memref_from_ptr with the extracted pointer (_ptr version)
1255 clean_target_type = mlir_type_target.strip('()')
1256 # Use the _ptr field_id for raw pointer types
1257 conversion_mlir, memref_id = SyntaxTree.Node.create_memref_from_ptr(
1258 field_id, # This is now %maxEigenvalues_ptr for raw pointers
1259 clean_target_type,
1260 indent_level,
1261 target_var_name=SyntaxTree.Node.target_variable_name)
1262 mlir_str += conversion_mlir
1263 final_id = memref_id
1264
1265 # Always append to context and return variable ID
1266 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1267 return final_id
1268 return string._value
1269
1270 def print_float(self, float: SyntaxTree.Float, indent_level = 0):
1271 indent = self.get_indent(indent_level)
1272
1273 if float._string is None:
1274 value = str(float._value)
1275 if float.hoist():
1276 float.mlir_id(SyntaxTree.Node.get_mlir_id())
1277 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1278 mlir_str += indent + f"%{float.mlir_id()} = arith.constant {float._value} : {float.get_type().print(self)}\n"
1279 SyntaxTree.Node.context.push_block()
1280 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1281 id = f"%{float.mlir_id()}"
1282 return id
1283 return value
1284 else:
1285 return str(float._string)
1286
1287 def print_data_block(self, data_block: SyntaxTree.DataBlock, indent_level=0):
1288 if data_block.id is None:
1289 return data_block._internal.print(self, indent_level)
1290 else:
1291 return "%" + data_block.id
1292
1293 def print_subscript(self, subscript: SyntaxTree.Subscript, indent_level=0):
1294 indent = self.get_indent(indent_level)
1295 dims = 1
1296 tmp = subscript._value
1297 while isinstance(tmp, SyntaxTree.Subscript):
1298 dims += 1
1299 tmp = tmp._value
1300
1301 subscript._dimensions = dims
1302 value_type = subscript._value.get_type()
1303 type_str = value_type.print(self.cpp_printer).strip()
1304
1305 mlir_type = SyntaxTree.Node.type_map.get(type_str)
1306 if mlir_type is None:
1307 # Fallback: try to get the MLIR type from the type object itsubscript, or use a default
1308 mlir_type = '' + value_type.print(self)
1309
1310 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1311
1312 subscript._index.hoist(True)
1313 index = subscript._index.print(self, indent_level)
1314 mlir_str += "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1315
1316 if not getattr(subscript._index, '_index_type', False):
1317 new_index = f"%{SyntaxTree.Node.get_mlir_id()}"
1318 # Determine the actual type of the index expression
1319 index_expr_type = subscript._index.get_type()
1320 mlir_type_cast = index_expr_type.print(self).strip()
1321 mlir_str += indent + new_index + f" = arith.index_cast {index.strip(' ')}: {mlir_type_cast} to index\n"
1322 subscript._new_index = new_index
1323 else:
1324 subscript._new_index = index
1325
1326 # NOTE: special handling: Strings might represent struct field access
1327 if isinstance(subscript._value, SyntaxTree.String):
1328 # When indexing a String field (like patchData.input[i]), we need to handle the index
1329 # Get the field type to determine if it's 2D or 1D
1330 base_mlir = subscript._value.print(self, indent_level)
1331 base_type = subscript._value.get_type()
1332
1333 if isinstance(base_type, SyntaxTree.TDataBlock):
1334 memref_type = base_type.print(self).strip()
1335
1336 # Check if it's a 2D flat or 1D nested memref
1337 if "x?x" in memref_type:
1338 # 2D flat memref - but we only have one index, which means we need a second one
1339 # This shouldn't happen for single-level subscripts on 2D arrays
1340 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1341 mlir_str += indent + f"{result_id} = memref.load {base_mlir}[%0, {subscript._new_index}] {{name = \"{base_mlir.strip('%')}\"}} : {memref_type}\n"
1342 SyntaxTree.Node.context.push_block()
1343 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1344 return result_id
1345 else:
1346 # 1D memref - can be nested (memref<?xmemref<?xf64>>) or plain (memref<?xf64>)
1347 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1348 mlir_str += indent + f"{result_id} = memref.load {base_mlir}[{subscript._new_index}] {{name = \"{base_mlir.strip('%')}\"}} : {memref_type}\n"
1349 SyntaxTree.Node.context.push_block()
1350 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1351 return result_id
1352 else:
1353 # Not a memref type, just return the base
1354 return base_mlir
1355
1356 SyntaxTree.Node.context.push_block()
1357 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1358
1359 subscript._value.hoist(True)
1360 base_value = subscript._value.print(self, indent_level)
1361
1362 # Handle nested subscripts (like source[i][j]) by generating proper load operations
1363 if subscript.is_nested_subscript():
1364 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1365
1366 base_expr = tmp # The base expression without subscripts
1367 outer_base = base_expr.print(self, indent_level) # The base array
1368
1369 # Get the actual type of the base expression - check mlir_memref_types FIRST
1370 if outer_base in SyntaxTree.Node.mlir_memref_types:
1371 actual_memref_type = SyntaxTree.Node.mlir_memref_types[outer_base]
1372 else:
1373 # Fallback to computing from type
1374 base_expr_type = base_expr.get_type()
1375 if isinstance(base_expr_type, SyntaxTree.TDataBlock):
1376 actual_memref_type = base_expr_type.print(self).strip()
1377 else:
1378 actual_memref_type = "memref<?x?xf64>" # Fallback
1379
1380 # Check if base expression has an explicit type in type_map (nested memref override)
1381 base_name = None
1382 if isinstance(base_expr, SyntaxTree.Name):
1383 base_name = base_expr.id
1384 elif hasattr(base_expr, 'id'):
1385 # DataBlock or similar object with id attribute
1386 base_name = base_expr.id
1387
1388 if base_name and base_name in SyntaxTree.Node.type_map and SyntaxTree._is_nested_memref(SyntaxTree.Node.type_map[base_name]):
1389 actual_memref_type = SyntaxTree.Node.type_map[base_name]
1390
1391 # Collect both indices from the nested structure
1392 indices = []
1393 current = subscript
1394 while isinstance(current, SyntaxTree.Subscript):
1395 indices.insert(0, current._new_index)
1396 current = current._value
1397
1398 # Generate load based on memref type structure
1399 indices_str = ", ".join(indices)
1400 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1401
1402 # Check if it's a nested memref (memref<?xmemref<...>>) vs flat multi-D (memref<?x?x...>)
1403 # Handle both Type objects and strings
1404 if isinstance(actual_memref_type, str):
1405 is_nested = actual_memref_type.count("memref<") > 1
1406 actual_memref_type_str = actual_memref_type
1407 else:
1408 # Type object (e.g., TNestedMemref)
1409 is_nested = actual_memref_type.is_nested_memref()
1410 actual_memref_type_str = actual_memref_type.print(self).strip()
1411
1412 if is_nested and len(indices) >= 2:
1413 # This is a nested memref (memref<?xmemref<?xf64>>)
1414 # Need to load in two steps: first get the inner memref, then load from it
1415 inner_memref_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1416 # Extract the inner memref type from the outer type
1417 if actual_memref_type_str.startswith("memref<?x") and actual_memref_type_str.endswith(">>"):
1418 inner_type = actual_memref_type_str[9:-1] # Remove "memref<?x" and final ">"
1419 else:
1420 inner_type = "memref<?xf64>" # Fallback
1421
1422 mlir_str += indent + f"{inner_memref_id} = memref.load {outer_base}[{indices[0]}] {{name = \"{outer_base.strip('%')}\"}} : {actual_memref_type_str}\n"
1423 # Now load from the inner memref with remaining indices
1424 remaining_indices = ", ".join(indices[1:])
1425 mlir_str += indent + f"{result_id} = memref.load {inner_memref_id}[{remaining_indices}] {{name = \"{outer_base.strip('%')}\"}} : {inner_type}\n"
1426 else:
1427 # This is a flat multi-D memref (memref<?x?xf64>, memref<?x?x?xf64>, etc.)
1428 mlir_str += indent + f"{result_id} = memref.load {outer_base}[{indices_str}] {{name = \"{outer_base.strip('%')}\"}} : {actual_memref_type_str}\n"
1429
1430 SyntaxTree.Node.context.push_block()
1431 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1432
1433 return result_id
1434
1435 # Default: just return the base value (may be a memref, pointer, or value)
1436 return base_value
1437
1438 def print_reference(self, reference: SyntaxTree.Reference, indent_level=0):
1439 result_id = None
1440 indent = self.get_indent(indent_level)
1441 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1442
1443 reference._expression.hoist(True)
1444
1445 # Handle references to subscripted expressions (array elements)
1446 if isinstance(reference._expression, SyntaxTree.Subscript):
1447 base_expr = reference._expression._value
1448 index_expr = reference._expression._index
1449
1450 base_expr.hoist(True)
1451 base_mlir = base_expr.print(self, indent_level)
1452
1453 mlir_str += "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1454
1455 if not hasattr(reference._expression, '_new_index'):
1456 # If _new_index doesn't exist, process index and generate it now
1457 index_expr.hoist(True)
1458 index_mlir = index_expr.print(self, indent_level)
1459 mlir_str += "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1460
1461 # Create new index with proper type cast
1462 new_index = f"%{SyntaxTree.Node.get_mlir_id()}"
1463 mlir_type = ('index' if hasattr(index_expr, '_index_type') and index_expr._index_type else 'i32')
1464 mlir_str += indent + new_index + f" = arith.index_cast {index_mlir.strip(' ')}: {mlir_type} to index\n"
1465 reference._expression._new_index = new_index
1466 else:
1467 new_index = reference._expression._new_index
1468
1469 # Determine dimensions and type of the memref
1470 base_type = base_expr.get_type()
1471 if isinstance(base_type, SyntaxTree.TDataBlock):
1472 result_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1473
1474 # For nested memrefs (like double**), handle the inner memref properly
1475 if isinstance(base_expr, SyntaxTree.Subscript):
1476 # base_expr is Subscript[DataBlock, i]
1477 # We need to check if the base_expr itreference is a nested subscript or just single-level
1478 if isinstance(base_expr._value, SyntaxTree.Subscript):
1479 result_id = base_mlir
1480 else:
1481 # Check if this variable has an explicit nested type in `type_map`
1482 base_mlir_name = base_mlir.lstrip('%')
1483 is_nested_memref = False
1484 if base_mlir_name in SyntaxTree.Node.type_map and SyntaxTree._is_nested_memref(SyntaxTree.Node.type_map[base_mlir_name]):
1485 is_nested_memref = True
1486
1487 outer_index = base_expr._new_index
1488
1489 # Use unified method from SyntaxTree.Node to generate pointer arithmetic
1490 ptr_code, result_id = SyntaxTree.Node.generate_pointer_to_2d_element(
1491 base_mlir, outer_index, new_index, is_nested_memref, indent_level
1492 )
1493
1494 mlir_str += ptr_code
1495 else:
1496 element_type = "f64" # Default to f64 - modify if needed for other types
1497 mlir_str += indent + f"{result_id} = memref.subview {base_mlir}[{new_index}][1][1] : memref<?x{element_type}> to memref<?x{element_type}>\n"
1498
1499 # Default behavior for non-subscript references
1500 SyntaxTree.Node.context.push_block()
1501 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1502
1503 # Process the expression and return its ID
1504 return result_id if result_id is not None else reference._expression.print(self, indent_level)
1505
1506 def print_vector(self, vector: SyntaxTree.Vector, indent_level=0):
1507 pass
1508
1509 def print_vector_index(self, vector_index: SyntaxTree.VectorIndex, indent_level=0):
1510 indent = self.get_indent(indent_level)
1511 mlir_str = ""
1512
1513 if isinstance(vector_index._value, SyntaxTree.Subscript):
1514 array_base = vector_index._value._value
1515 array_index = vector_index._value._index
1516 array_base_mlir = array_base.print(self, indent_level)
1517 array_index_mlir = array_index.print(self, indent_level)
1518 # Ensure array_index is of type 'index' with correct source type
1519 if not getattr(array_index, '_index_type', False):
1520 array_index_cast = f"%{SyntaxTree.Node.get_mlir_id()}"
1521 array_index_type = array_index.get_type()
1522 array_index_mlir_type = array_index_type.print(self).strip()
1523 mlir_str += indent + f"{array_index_cast} = arith.index_cast {array_index_mlir} : {array_index_mlir_type} to index\n"
1524 else:
1525 array_index_cast = array_index_mlir
1526
1527 raw_ptr = f"%{SyntaxTree.Node.get_mlir_id()}"
1528 mlir_str += indent + f"{raw_ptr} = memref.load {array_base_mlir}[{array_index_cast}] {{name = \"{array_base_mlir.strip('%')}\"}} : memref<?xi64>\n"
1529
1530 vec_ptr = f"%{SyntaxTree.Node.get_mlir_id()}"
1531 mlir_str += indent + f"{vec_ptr} = llvm.inttoptr {raw_ptr} {{name = \"{array_base_mlir.strip('%')}\"}} : i64 to !llvm.ptr\n"
1532
1533 func_param_mlir = vector_index._index.print(self, indent_level)
1534 # Ensure func_param is i32 (emit arith.constant for Integer, cast otherwise)
1535 if isinstance(vector_index._index, SyntaxTree.Integer):
1536 func_param_i32 = f"%{SyntaxTree.Node.get_mlir_id()}"
1537 mlir_str += indent + f"{func_param_i32} = arith.constant {int(vector_index._index._value)} : i32\n"
1538 elif not getattr(vector_index._index, '_index_type', False):
1539 func_param_i32 = f"%{SyntaxTree.Node.get_mlir_id()}"
1540 mlir_str += indent + f"{func_param_i32} = arith.index_cast {func_param_mlir} : index to i32\n"
1541 else:
1542 func_param_i32 = func_param_mlir
1543
1544 result = f"%{SyntaxTree.Node.get_mlir_id()}"
1545 # Convert i32 index to i64 for pointer arithmetic
1546 func_param_i64 = f"%{SyntaxTree.Node.get_mlir_id()}"
1547 mlir_str += indent + f"{func_param_i64} = arith.extsi {func_param_i32} : i32 to i64\n"
1548 # Compute pointer to array element
1549 velem_ptr = f"%{SyntaxTree.Node.get_mlir_id()}"
1550 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"
1551 # Load the value from the computed pointer
1552 mlir_str += indent + f"{result} = llvm.load {velem_ptr} {{name = \"{array_base_mlir.strip('%')}\"}} : !llvm.ptr -> f64\n"
1553 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1554
1555 return result
1556
1557 def print_comparison(self, comparison: SyntaxTree.Comparison, indent_level=0):
1558 op_type_class = SyntaxTree.TDataBlock.get_mlir_type_for_operation(comparison._lhs, comparison._rhs)
1559
1560 mlir_operation = Comparison.get_mlir_comparison_op(comparison._operation, op_type_class)
1561 result_type = "i1" # Comparisons always return boolean
1562
1563 if mlir_operation is None:
1564 raise ValueError(f"Unsupported comparison operation: {comparison._operation}")
1565
1566 const_zero_id = None
1567
1568 lhs_mlir = comparison._lhs.print(self, indent_level)
1569 if SyntaxTree.TDataBlock.needs_memref_load(comparison._lhs):
1570 const_zero_id = SyntaxTree.TDataBlock.ensure_const_zero(const_zero_id, indent_level)
1571 lhs_mlir = SyntaxTree.TDataBlock.generate_memref_load(comparison._lhs, lhs_mlir, const_zero_id, indent_level)
1572
1573 rhs_mlir = comparison._rhs.print(self, indent_level)
1574 if SyntaxTree.TDataBlock.needs_memref_load(comparison._rhs):
1575 const_zero_id = SyntaxTree.TDataBlock.ensure_const_zero(const_zero_id, indent_level)
1576 rhs_mlir = SyntaxTree.TDataBlock.generate_memref_load(comparison._rhs, rhs_mlir, const_zero_id, indent_level)
1577
1578 return self.get_indent(indent_level) + f"""{mlir_operation} {lhs_mlir}, {rhs_mlir} : {result_type}"""
1579
1580 def print_binary_operation(self, binary_operation: SyntaxTree.BinaryOperation, indent_level=0):
1581 indent = self.get_indent(indent_level)
1582 binary_operation._lhs.hoist(True)
1583 binary_operation._rhs.hoist(True)
1584
1585 op_type_class = SyntaxTree.TDataBlock.get_mlir_type_for_operation(binary_operation._lhs, binary_operation._rhs)
1586 result_type = op_type_class().print_mlir()
1587
1588 op_type_class = SyntaxTree.TDataBlock.get_mlir_type_for_operation(binary_operation._lhs, binary_operation._rhs)
1589 result_type = op_type_class().print(self)
1590
1591 const_zero_id = None
1592
1593 binary_operation._lhs.hoist(True)
1594 lhs = binary_operation._lhs.print(self, indent_level).strip(' ')
1595
1596 if SyntaxTree.TDataBlock.needs_memref_load(binary_operation._lhs):
1597 const_zero_id = SyntaxTree.TDataBlock.ensure_const_zero(const_zero_id, indent_level)
1598 lhs = SyntaxTree.TDataBlock.generate_memref_load(binary_operation._lhs, lhs, const_zero_id, indent_level)
1599
1600 lhs = SyntaxTree.BinaryOperation.cast_operand(lhs, binary_operation._lhs, op_type_class, indent_level)
1601
1602 binary_operation._rhs.hoist(True)
1603 rhs = binary_operation._rhs.print(self, indent_level).strip(' ')
1604
1605 if SyntaxTree.TDataBlock.needs_memref_load(binary_operation._rhs):
1606 const_zero_id = SyntaxTree.TDataBlock.ensure_const_zero(const_zero_id, indent_level)
1607 rhs = SyntaxTree.TDataBlock.generate_memref_load(binary_operation._rhs, rhs, const_zero_id, indent_level)
1608
1609 rhs = SyntaxTree.BinaryOperation.cast_operand(rhs, binary_operation._rhs, op_type_class, indent_level)
1610
1611 mlir_str = ''
1612 for mlir in SyntaxTree.Node.context.pop_block().get_mlir():
1613 mlir_str += indent + mlir.strip(' ')
1614
1615 binary_operation.mlir_id(f"%{SyntaxTree.Node.get_mlir_id()}")
1616
1617 mlir_str += indent + f"""{binary_operation.mlir_id()} = {binary_operation.get_mlir_op()} {lhs}, {rhs} : {result_type}\n"""
1618 SyntaxTree.Node.context.push_block()
1619 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1620
1621 return binary_operation.mlir_id()
1622
1623 def print_unary_operation(self, unary_operation: SyntaxTree.UnaryOperation, indent_level=0):
1624 indent = self.get_indent(indent_level)
1625 unary_operation._operand.hoist(True)
1626
1627 operand_type = unary_operation._operand.get_type()
1628 if isinstance(operand_type, SyntaxTree.TDataBlock):
1629 element_type_class = operand_type.get_element_type()
1630 operand_type = element_type_class() if element_type_class else operand_type
1631
1632 if isinstance(operand_type, SyntaxTree.TFloat):
1633 if unary_operation._operation == "+":
1634 SyntaxTree.Node.context.get_block().mlir_append(unary_operation._operand.print(self, indent_level))
1635 return unary_operation.mlir_id()
1636 elif unary_operation._operation == "-":
1637 op_mlir = unary_operation._operand.print(self, indent_level).strip(' ')
1638 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1639 neg_one_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1640 mlir_str += indent + f"{neg_one_id} = arith.constant -1.0 : f64\n"
1641
1642 unary_operation.mlir_id(f"%{SyntaxTree.Node.get_mlir_id()}")
1643 mlir_str += indent + f"{unary_operation.mlir_id()} = arith.mulf {op_mlir}, {neg_one_id} : f64\n"
1644 SyntaxTree.Node.context.push_block()
1645 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1646 return unary_operation.mlir_id()
1647 else:
1648 if unary_operation._operation == "+":
1649 SyntaxTree.Node.context.get_block().mlir_append(unary_operation._operand.print(self, indent_level))
1650 return unary_operation.mlir_id()
1651 elif unary_operation._operation == "-":
1652 op_mlir = unary_operation._operand.print(self, indent_level).strip(' ')
1653 mlir_str = "".join(SyntaxTree.Node.context.get_block().pop_mlir())
1654 neg_one_id = f"%{SyntaxTree.Node.get_mlir_id()}"
1655 mlir_str += indent + f"{neg_one_id} = arith.constant -1 : i32\n"
1656
1657 unary_operation.mlir_id(f"%{SyntaxTree.Node.get_mlir_id()}")
1658 mlir_str += indent + f"{unary_operation.mlir_id()} = arith.muli {op_mlir}, {neg_one_id} : i32\n"
1659 SyntaxTree.Node.context.push_block()
1660 SyntaxTree.Node.context.get_block().mlir_append(mlir_str)
1661 return unary_operation.mlir_id()
1662
1663 def print_data_block_unary_max(self, unary_max: SyntaxTree.DataBlockUnaryMax, indent_level=0):
1664 return unary_max._loop.print(self, indent_level)
1665
1666 def print_custom_type(self, type: SyntaxTree.TCustom, indent_level=0):
1667 mlir_type = SyntaxTree.Node.get_mlir_type(type._type)
1668 if mlir_type is not None:
1669 return ' ' * indent_level * SyntaxTree.Node.spaces_per_tab + mlir_type
1670 else:
1671 # Fallback to type_map for complex types
1672 type_entry = SyntaxTree.Node.type_map[f"{type._type}"]
1673 if isinstance(type_entry, str):
1674 return self.get_indent(indent_level) + type_entry
1675 else:
1676 # Type object (e.g., TVoid, TFloat, etc.)
1677 return self.get_indent(indent_level) + type_entry.print(self)
1678
1679 def print_void_type(self, type: SyntaxTree.TVoid, indent_level=0):
1680 return self.get_indent(indent_level) + 'void'
1681
1682 def print_integer_type(self, type: SyntaxTree.TInteger, indent_level=0):
1683 return self.get_indent(indent_level) + "i32"
1684
1685 def print_long_type(self, type: SyntaxTree.TLong, indent_level=0):
1686 return self.get_indent(indent_level) + "i64"
1687
1688 def print_boolean_type(self, type: SyntaxTree.TBoolean, indent_level=0):
1689 return self.get_indent(indent_level) + "i1"
1690
1691 def print_float_type(self, type: SyntaxTree.TFloat, indent_level=0):
1692 return self.get_indent(indent_level) + "f64"
1693
1694 def print_string_type(self, type: SyntaxTree.TString, indent_level=0):
1695 pass
1696
1697 def print_data_block_type(self, type: SyntaxTree.TString, indent_level=0):
1698 dims = []
1699
1700 if isinstance(type._dimensions, int):
1701 dims.append(str(type._dimensions))
1702 else:
1703 for dim in type._dimensions:
1704 match dim:
1705 case int():
1706 dims.append(str(dim))
1707 case Expression():
1708 dims.append(dim.print(self, indent_level))
1709 case list():
1710 SyntaxTree.Node.context.push_block()
1711 for op in dim:
1712 op.print(self, indent_level)
1713 dims.append(''.join(SyntaxTree.Node.context.pop_block().get_mlir()))
1714
1715 # NOTE: The underlying type isn't an MLIR type here but a string representing the C++ type. So...
1716 # We look up the C++ type string in a types map in SyntaxTree.Node and get the MLIR type
1717 if isinstance(type._underlying_type, SyntaxTree.Type):
1718 type_name = type._underlying_type.print(self, indent_level)
1719 else:
1720 # We have a String type here
1721 type_name = SyntaxTree.Node.get_mlir_type(type._underlying_type._value)
1722 if type_name is None:
1723 # Fallback to type_map for complex types
1724 type_name = SyntaxTree.Node.type_map[f"{type._underlying_type}"]
1725
1726 # If type_name is already a memref type (e.g., from Vector types that map to 'memref<?xi64>'),
1727 # return it directly instead of wrapping it again. This prevents creating invalid nested memrefs like 'memref<?xmemref<?xi64>>'.
1728 if isinstance(type_name, str) and type_name.startswith("memref<"):
1729 return ' ' * indent_level * SyntaxTree.Node.spaces_per_tab + type_name
1730
1731 # Build the memref type with the correct number of dimensions
1732 # NOTE: type._dimensions may include halo dimensions, so we need to infer actual data dimensions
1733 # For now, we use a heuristic: if 1D field, return 1D; if multi-D, assume 2D (common case)
1734 # The type_map provides the correct types for named fields, so this should rarely be used
1735
1736 if isinstance(type._dimensions, int):
1737 if type._dimensions == 1:
1738 actual_dims = 1
1739 else:
1740 # Multi-dimensional iteration ranges with halo are typically 2D or 3D data
1741 # For now, assume 2D as fallback
1742 actual_dims = 2
1743 elif isinstance(type._dimensions, list):
1744 if len(type._dimensions) <= 1:
1745 actual_dims = 1
1746 else:
1747 actual_dims = 2 # Fallback for multi-dim
1748 else:
1749 actual_dims = 1 # Fallback
1750
1751 # Build memref with ? for each actual dimension
1752 dimension_list = ["?"] * actual_dims
1753 memref_dims = "x".join(dimension_list)
1754
1755 return self.get_indent(indent_level) + f"""memref<{memref_dims}x{type_name}>"""
1756
1757 def print_vector_type(self, type: SyntaxTree.TVector, indent_level=0):
1758 return self.get_indent(indent_level) + f'memref<?x{type._element_type.print(self).strip()}>'
1759
1760 def print_nested_memref_type(self, type: SyntaxTree.TNestedMemref, indent_level=0):
1761 return self.get_indent(indent_level) + f'memref<?xmemref<?x{type._element_type.print(self).strip()}>>'
1762
1763 def print_simple_memref_type(self, type: SyntaxTree.TSimpleMemref, indent_level=0):
1764 # Build memref<?x...xf64> with appropriate number of dimensions
1765 dims = 'x'.join(['?'] * type._dimensions)
1766 return self.get_indent(indent_level) + f'memref<{dims}x{type._element_type.print(self).strip()}>' # Use print_mlir for element type
1767
1768 def print_function_type(self, type: SyntaxTree.TFunctionType, indent_level=0):
1769 return self.get_indent(indent_level) + type._signature
We need to track the context of the code generation to support MLIR class Context:
print_integer_type(self, SyntaxTree.TInteger type, indent_level=0)
print_data_block_construction_from_matrix_vector_product(self, SyntaxTree.DataBlockConstructionFromFunction construction, indent_level=0)
print_argument(self, SyntaxTree.Argument argument, indent_level=0)
print_matrix(self, SyntaxTree.Matrix construction, indent_level=0)
print_string(self, SyntaxTree.String string, indent_level=0)
print_if(self, SyntaxTree.If if_statement, indent_level=0)
print_data_block_construction_from_operation(self, SyntaxTree.DataBlockConstructionFromOperation construction, indent_level=0)
print_string_type(self, SyntaxTree.TString type, indent_level=0)
print_binary_operation(self, SyntaxTree.BinaryOperation binary_operation, indent_level=0)
print_boolean_type(self, SyntaxTree.TBoolean type, indent_level=0)
print_integer(self, SyntaxTree.Integer integer, indent_level=0)
print_custom_type(self, SyntaxTree.TCustom type, indent_level=0)
print_function_call(self, SyntaxTree.FunctionCall function_call, indent_level=0)
str get_indent(self, int indent_level)
print_nested_memref_type(self, SyntaxTree.TNestedMemref type, indent_level=0)
print_diagonal_matrix_construction_from_value(self, SyntaxTree.DataBlockConstructionFromFunction construction, indent_level=0)
print_vector(self, SyntaxTree.Vector vector, indent_level=0)
print_float(self, SyntaxTree.Float float, indent_level=0)
print_data_block_type(self, SyntaxTree.TString type, indent_level=0)
print_memory_deallocation(self, SyntaxTree.MemoryDeallocation memory_deallocation, indent_level=0)
print_function_declaration(self, SyntaxTree.FunctionDefinition function_definition, indent_level=0)
print_boolean(self, SyntaxTree.Boolean boolean, indent_level=0)
print_diagonal_matrix_construction_from_kronecker_product(self, SyntaxTree.DataBlockConstructionFromFunction construction, indent_level=0)
print_max(self, SyntaxTree.Max max, indent_level=0)
print_unary_operation(self, SyntaxTree.UnaryOperation unary_operation, indent_level=0)
print_data_block_construction_from_dot_product(self, SyntaxTree.DataBlockConstructionFromFunction construction, indent_level=0)
print_function_definition(self, SyntaxTree.FunctionDefinition function_definition, indent_level=0)
print_memory_allocation(self, SyntaxTree.MemoryAllocation memory_allocation, indent_level=0)
print_function_declaration_with_timer(self, SyntaxTree.FunctionDefinition function_definition, indent_level=0)
print_comment(self, SyntaxTree.Comment comment, indent_level=0)
print_simple_memref_type(self, SyntaxTree.TSimpleMemref type, indent_level=0)
print_name(self, SyntaxTree.Name name, indent_level=0)
print_void_type(self, SyntaxTree.TVoid type, indent_level=0)
print_reference(self, SyntaxTree.Reference reference, indent_level=0)
print_function_type(self, SyntaxTree.TFunctionType type, indent_level=0)
print_data_block_construction_from_existing(self, SyntaxTree.DataBlockConstructionFromExisting construction, indent_level=0)
print_matrix_construction_from_kronecker_product(self, SyntaxTree.DataBlockConstructionFromFunction construction, indent_level=0)
print_data_block_unary_max(self, SyntaxTree.DataBlockUnaryMax unary_max, indent_level=0)
print_diagonal_matrix(self, SyntaxTree.DiagonalMatrix matrix, indent_level=0)
print_vector_index(self, SyntaxTree.VectorIndex vector_index, indent_level=0)
print_data_block_construction_from_function(self, SyntaxTree.DataBlockConstructionFromFunction construction, indent_level=0)
print_function_definition_with_timer(self, SyntaxTree.FunctionDefinition function_definition, indent_level=0)
print_comparison(self, SyntaxTree.Comparison comparison, indent_level=0)
__init__(self, spaces_per_tab=2, use_omp=False)
print_data_block_assignment(self, SyntaxTree.DataBlockAssignment assignment, indent_level=0)
print_long_type(self, SyntaxTree.TLong type, indent_level=0)
print_subscript(self, SyntaxTree.Subscript subscript, indent_level=0)
print_assignment(self, SyntaxTree.Assignment assignment, indent_level=0)
print_data_block(self, SyntaxTree.DataBlock data_block, indent_level=0)
print_float_type(self, SyntaxTree.TFloat type, indent_level=0)
print_vector_type(self, SyntaxTree.TVector type, indent_level=0)
print_construction(self, SyntaxTree.Construction construction, indent_level=0)
print_for(self, SyntaxTree.For for_statement, indent_level=0)