diff --git a/python/quadrants/lang/ast/ast_transformer_utils.py b/python/quadrants/lang/ast/ast_transformer_utils.py index e2a6ac6d03..96b0da67c5 100644 --- a/python/quadrants/lang/ast/ast_transformer_utils.py +++ b/python/quadrants/lang/ast/ast_transformer_utils.py @@ -35,7 +35,11 @@ def __call__(self, ctx: "ASTTransformerFuncContext", node: ast.AST): if method is None: error_msg = f'Unsupported node "{node.__class__.__name__}"' raise QuadrantsSyntaxError(error_msg) - info = ctx.get_pos_info(node) if isinstance(node, (ast.stmt, ast.expr)) else "" + # Attach only the cheap file/line/function header to every IR node. Building the full source-line + # hint (get_pos_info) here means running TextWrapper for every AST node of every kernel compilation, + # which dominates kernel build time. The full hint is still produced on the actual compile-error path + # below (get_pos_info in the except handler), so error messages are unchanged. + info = ctx.get_pos_header(node) if isinstance(node, (ast.stmt, ast.expr)) else "" with impl.get_runtime().src_info_guard(info): res = method(ctx, node) if not hasattr(node, "violates_pure"): @@ -387,8 +391,11 @@ def get_var_by_name(self, name: str) -> tuple[bool, Any, str | None]: except AttributeError: raise QuadrantsNameError(f'Name "{name}" is not defined') + def get_pos_header(self, node: ast.AST) -> str: + return f'File "{self.file}", line {node.lineno + self.lineno_offset}, in {self.func.func.__name__}:\n' + def get_pos_info(self, node: ast.AST) -> str: - msg = f'File "{self.file}", line {node.lineno + self.lineno_offset}, in {self.func.func.__name__}:\n' + msg = self.get_pos_header(node) col_offset = self.indent + node.col_offset end_col_offset = self.indent + node.end_col_offset diff --git a/python/quadrants/lang/kernel_impl.py b/python/quadrants/lang/kernel_impl.py index d52222a6e9..94c08513e1 100644 --- a/python/quadrants/lang/kernel_impl.py +++ b/python/quadrants/lang/kernel_impl.py @@ -1,4 +1,4 @@ -import inspect +import linecache import re import sys import typing @@ -141,10 +141,13 @@ def pyfunc(fn: Callable) -> QuadrantsCallable: def _inside_class(level_of_class_stackframe: int) -> bool: try: maybe_class_frame = sys._getframe(level_of_class_stackframe) - statement_list = inspect.getframeinfo(maybe_class_frame)[3] - if statement_list is None: + # Read the decoration-site source line via linecache rather than inspect.getframeinfo: getframeinfo + # resolves the frame's module through inspect.getmodule, an O(len(sys.modules)) scan run once per kernel + # creation. With thousands of modules loaded this dominates kernel build time; linecache.getline returns + # the same source line with no such scan. + first_statment = linecache.getline(maybe_class_frame.f_code.co_filename, maybe_class_frame.f_lineno).strip() + if not first_statment: return False - first_statment = statement_list[0].strip() for pat in _KERNEL_CLASS_STACKFRAME_STMT_RES: if pat.match(first_statment): return True