1111import hashlib
1212import numpy as np
1313import pyxrt as xrt
14+ import shutil
15+ import sys
16+ import traceback
1417
1518from aie .extras .context import mlir_mod_ctx
1619from ..utils .compile import compile_mlir_module_to_binary
1720from ..utils .xrt import read_insts_binary
21+ from .device import NPU1 , NPU2 , NPU1Col1 , NPU2Col1
22+ from .config import get_current_device
23+ from aie .dialects .aie import AIEDevice
1824
1925
2026# The `iron.jit` decorator below caches compiled kenrels inside the `IRON_CACHE_DIR` directory.
@@ -93,11 +99,15 @@ def __call__(self, *args):
9399 kernel_args = []
94100
95101 for tensor in args :
102+ # Skip callable arguments since these are inlined in the kernel
103+ if callable (tensor ):
104+ continue
96105 if not hasattr (tensor , "buffer_object" ):
97106 raise TypeError (
98107 f"Expected Tensor with .buffer_object(), got { type (tensor )} "
99108 )
100109 kernel_args .append (tensor .buffer_object ())
110+
101111 h = self .__kernel (opcode , self .__insts_buffer_bo , self .__n_insts , * kernel_args )
102112 r = h .wait ()
103113 if r != xrt .ert_cmd_state .ERT_CMD_STATE_COMPLETED :
@@ -133,18 +143,61 @@ def jit(function=None, is_placed=True, use_cache=True):
133143
134144 @functools .wraps (function )
135145 def decorator (* args , ** kwargs ):
136- if is_placed :
137- with mlir_mod_ctx () as ctx :
138- function (* args , ** kwargs )
139- assert (
140- ctx .module .operation .verify ()
141- ), f"Verification failed for '{ function .__name__ } '"
142- mlir_module = ctx .module
143- else :
144- mlir_module = function (* args , ** kwargs )
146+ from .kernel import ExternalFunction
147+
148+ # Clear any instances from previous runs to make sure if the user provided any broken code we don't try to recompile it
149+ ExternalFunction ._instances .clear ()
150+
151+ # Find ExternalFunction instances in arguments and kwargs
152+ external_kernels = []
153+ for arg in args :
154+ if isinstance (arg , ExternalFunction ):
155+ external_kernels .append (arg )
156+ for value in kwargs .values ():
157+ if isinstance (value , ExternalFunction ):
158+ external_kernels .append (value )
145159
146- # Hash of the IR string
147- module_hash = hash_module (mlir_module )
160+ # Execute the function to generate MLIR
161+ try :
162+ if is_placed :
163+ with mlir_mod_ctx () as ctx :
164+ function (* args , ** kwargs )
165+ assert (
166+ ctx .module .operation .verify ()
167+ ), f"Verification failed for '{ function .__name__ } '"
168+ mlir_module = ctx .module
169+ else :
170+ mlir_module = function (* args , ** kwargs )
171+ except Exception as e :
172+ raise
173+
174+ # Compile all ExternalFunction instances that were created during this JIT compilation
175+ for func in ExternalFunction ._instances :
176+ if (
177+ not hasattr (func , "_compiled" ) or not func ._compiled
178+ ): # Don't compile if already compiled
179+ external_kernels .append (func )
180+
181+ # Determine target architecture based on device type
182+ try :
183+ current_device = get_current_device ()
184+
185+ # Determine target architecture based on device type
186+ if isinstance (current_device , (NPU2 , NPU2Col1 )):
187+ target_arch = "aie2p"
188+ elif isinstance (current_device , (NPU1 , NPU1Col1 )):
189+ target_arch = "aie2"
190+ elif current_device in (AIEDevice .npu2 , AIEDevice .npu2_1col ):
191+ target_arch = "aie2p"
192+ elif current_device in (AIEDevice .npu1 , AIEDevice .npu1_1col ):
193+ target_arch = "aie2"
194+ else :
195+ raise RuntimeError (f"Unsupported device type: { type (current_device )} " )
196+ except Exception as e :
197+ raise
198+
199+ # Hash of the IR string, ExternalFunction compiler options, and target architecture
200+ module_hash = hash_module (mlir_module , external_kernels , target_arch )
148201 kernel_dir = os .path .join (IRON_CACHE_DIR , f"{ module_hash } " )
149202 mlir_path = os .path .join (kernel_dir , "aie.mlir" )
150203
@@ -161,25 +214,120 @@ def decorator(*args, **kwargs):
161214 inst_exists = os .path .exists (inst_path )
162215
163216 if not use_cache or not xclbin_exists or not inst_exists :
164- with open (mlir_path , "w" , encoding = "utf-8" ) as f :
165- print (mlir_module , file = f )
166- compile_mlir_module_to_binary (
167- mlir_module = mlir_module ,
168- inst_path = inst_path ,
169- xclbin_path = xclbin_path ,
170- )
217+ try :
218+ with open (mlir_path , "w" , encoding = "utf-8" ) as f :
219+ print (mlir_module , file = f )
220+
221+ # Compile ExternalFunctions from inside the JIT compilation directory
222+ for func in external_kernels :
223+ compile_external_kernel (func , kernel_dir , target_arch )
224+
225+ # Compile the MLIR module
226+ compile_mlir_module_to_binary (
227+ mlir_module = mlir_module ,
228+ inst_path = inst_path ,
229+ xclbin_path = xclbin_path ,
230+ work_dir = kernel_dir ,
231+ )
232+ except Exception as e :
233+ # Clean up cache directory on any compilation failure to avoid any corrupted objects in the cache
234+ if os .path .exists (kernel_dir ):
235+ shutil .rmtree (kernel_dir )
236+ raise e
171237
172238 kernel_name = "MLIR_AIE"
173- return NPUKernel (xclbin_path , inst_path , kernel_name = kernel_name )(
174- * args , ** kwargs
175- )
239+ try :
240+ kernel = NPUKernel (xclbin_path , inst_path , kernel_name = kernel_name )
241+ result = kernel (* args , ** kwargs )
242+ return result
243+ except Exception as e :
244+ raise
176245
177246 return decorator
178247
179248
180- def hash_module ( module ):
249+ def compile_external_kernel ( func , kernel_dir , target_arch ):
181250 """
182- Hash the MLIR module to create a unique identifier.
251+ Compile an ExternalFunction to an object file in the kernel directory.
252+
253+ Args:
254+ func: ExternalFunction instance to compile
255+ kernel_dir: Directory to place the compiled object file
256+ target_arch: Target architecture (e.g., "aie2" or "aie2p")
257+ """
258+ # Skip if already compiled
259+ if hasattr (func , "_compiled" ) and func ._compiled :
260+ return
261+
262+ # Check if object file already exists in kernel directory
263+ output_file = os .path .join (kernel_dir , func ._object_file_name )
264+ if os .path .exists (output_file ):
265+ return
266+
267+ # Create source file in kernel directory
268+ source_file = os .path .join (kernel_dir , f"{ func ._name } .cc" )
269+
270+ # Handle both source_string and source_file cases
271+ if func ._source_string is not None :
272+ # Use source_string (write to file)
273+ try :
274+ with open (source_file , "w" ) as f :
275+ f .write (func ._source_string )
276+ except Exception as e :
277+ raise
278+ elif func ._source_file is not None :
279+ # Use source_file (copy existing file)
280+ # Check if source file exists before copying
281+ if os .path .exists (func ._source_file ):
282+ try :
283+ shutil .copy2 (func ._source_file , source_file )
284+ except Exception as e :
285+ raise
286+ else :
287+ return
288+ else :
289+ raise ValueError ("Neither source_string nor source_file is provided" )
290+
291+ from .compile .compile import compile_cxx_core_function
292+
293+ try :
294+ compile_cxx_core_function (
295+ source_path = source_file ,
296+ target_arch = target_arch ,
297+ output_path = output_file ,
298+ include_dirs = func ._include_dirs ,
299+ compile_args = func ._compile_flags ,
300+ cwd = kernel_dir ,
301+ verbose = False ,
302+ )
303+ except Exception as e :
304+ raise
305+
306+ # Mark the function as compiled
307+ func ._compiled = True
308+
309+
310+ def hash_module (module , external_kernels = None , target_arch = None ):
311+ """
312+ Hash the MLIR module and ExternalFunction compiler options to create a unique identifier.
183313 """
184314 mlir_str = str (module )
185- return hashlib .sha256 (mlir_str .encode ("utf-8" )).hexdigest ()[:16 ]
315+
316+ # Include ExternalFunction compiler options in the hash
317+ if external_kernels :
318+ compiler_options = []
319+ for func in external_kernels :
320+ compiler_options .extend (func ._include_dirs )
321+ compiler_options .extend (func ._compile_flags )
322+
323+ # Create a combined string for hashing
324+ combined_str = mlir_str + "|" + "|" .join (compiler_options )
325+ else :
326+ combined_str = mlir_str
327+
328+ # Include target architecture in the hash
329+ if target_arch :
330+ combined_str += f"|target_arch={ target_arch } "
331+
332+ hash_result = hashlib .sha256 (combined_str .encode ("utf-8" )).hexdigest ()[:16 ]
333+ return hash_result
0 commit comments