diff --git a/loki/batch/item.py b/loki/batch/item.py index 1ce98917f..3ce14b39b 100644 --- a/loki/batch/item.py +++ b/loki/batch/item.py @@ -685,7 +685,7 @@ def _dependencies(self): inline_calls = tuple({ call.function.name: call.function for call in FindInlineCalls().visit(self.ir.ir) - if isinstance(call.function, ProcedureSymbol) + if isinstance(call.function, ProcedureSymbol) and not call.function.type.is_intrinsic }.values()) imports = tuple( imprt for imprt in self.ir.imports diff --git a/loki/expression/mappers.py b/loki/expression/mappers.py index 57083faa9..441965759 100644 --- a/loki/expression/mappers.py +++ b/loki/expression/mappers.py @@ -559,6 +559,7 @@ def map_variable_symbol(self, expr, *args, **kwargs): new_type = expr.type if recurse_to_declaration_attributes: old_type = expr.type + assert expr.type is not None kind = self.rec(old_type.kind, *args, **kwargs) if expr.scope and expr.name == old_type.initial: diff --git a/loki/expression/tests/test_expression.py b/loki/expression/tests/test_expression.py index b8f3b54b5..2722d455b 100644 --- a/loki/expression/tests/test_expression.py +++ b/loki/expression/tests/test_expression.py @@ -79,14 +79,53 @@ def test_math_intrinsics(tmp_path, frontend): """ filepath = tmp_path/(f'expression_math_intrinsics_{frontend}.f90') routine = Subroutine.from_source(fcode, frontend=frontend) - function = jit_compile(routine, filepath=filepath, objname='math_intrinsics') + for assign in FindNodes(ir.Assignment).visit(routine.body): + assert isinstance(assign.rhs, sym.InlineCall) + assert isinstance(assign.rhs.function, sym.ProcedureSymbol) + assert assign.rhs.function.type.dtype.is_intrinsic + + # Test full functionality via JIT example + function = jit_compile(routine, filepath=filepath, objname='math_intrinsics') vmin, vmax, vabs, vexp, vsqrt, vlog = function(2., 4.) assert vmin == 2. and vmax == 4. and vabs == 2. assert vexp == np.exp(6.) and vsqrt == np.sqrt(6.) and vlog == np.log(6.) clean_test(filepath) +@pytest.mark.parametrize('frontend', available_frontends()) +def test_general_intrinsics(frontend): + """ + Test general intrinsic functions (size, shape, ubound, lbound, + allocated, trim, kind) + """ + fcode = """ +subroutine general_intrinsics(arr, ptr, name) + implicit none + real(kind=8), intent(inout) :: arr(:,:) + real(kind=8), pointer, intent(inout) :: ptr(:,:) + character(len=*), intent(inout) :: name + integer :: isize, ishape(:), ilower, iupper, mykind + logical :: alloc + character(len=*) :: myname + + isize = size(arr) + ishape = shape(arr) + ilower = lbound(arr) + iupper = ubound(arr) + mykind = kind(arr) + alloc = allocated(ptr) + myname = trim(name) +end subroutine general_intrinsics +""" + routine = Subroutine.from_source(fcode, frontend=frontend) + + for assign in FindNodes(ir.Assignment).visit(routine.body): + assert isinstance(assign.rhs, sym.InlineCall) + assert isinstance(assign.rhs.function, sym.ProcedureSymbol) + assert assign.rhs.function.type.dtype.is_intrinsic + + @pytest.mark.parametrize('frontend', available_frontends()) def test_logicals(tmp_path, frontend): """ diff --git a/loki/frontend/fparser.py b/loki/frontend/fparser.py index 865bcb8f8..693a89aa4 100644 --- a/loki/frontend/fparser.py +++ b/loki/frontend/fparser.py @@ -2547,7 +2547,20 @@ def visit_Function_Reference(self, o, **kwargs): def visit_Intrinsic_Function_Reference(self, o, **kwargs): + + # Register the ProcedureType in the scope before the name lookup + pname = o.children[0].string + scope = kwargs['scope'] + if not scope.get_symbol_scope(pname): + # No known alternative definition; register a true intrinsic procedure type + proc_type = ProcedureType( + name=pname, is_function=True, is_intrinsic=True, procedure=None + ) + kwargs['scope'].symbol_attrs[pname] = SymbolAttributes(dtype=proc_type, is_intrinsic=True) + + # Look up the function symbol name = self.visit(o.children[0], **kwargs) + if o.children[1] is not None: arguments = self.visit(o.children[1], **kwargs) kwarguments = tuple(arg for arg in arguments if isinstance(arg, tuple)) diff --git a/loki/frontend/omni.py b/loki/frontend/omni.py index 08c6cbf2b..833cb5ffa 100644 --- a/loki/frontend/omni.py +++ b/loki/frontend/omni.py @@ -1210,6 +1210,15 @@ def visit_FarrayConstructor(self, o, **kwargs): return sym.LiteralList(values=values, dtype=dtype) def visit_functionCall(self, o, **kwargs): + + if 'is_intrinsic' in o.attrib: + # Register the ProcedureType in the scope before the name lookup + pname = o.find('name').text + proc_type = ProcedureType( + name=pname, is_function=True, is_intrinsic=True, procedure=None + ) + kwargs['scope'].symbol_attrs[pname] = SymbolAttributes(dtype=proc_type, is_intrinsic=True) + if o.find('name') is not None: name = self.visit(o.find('name'), **kwargs) elif o.find('FmemberRef') is not None: diff --git a/loki/frontend/tests/test_frontends.py b/loki/frontend/tests/test_frontends.py index 07f04982d..86ea0762b 100644 --- a/loki/frontend/tests/test_frontends.py +++ b/loki/frontend/tests/test_frontends.py @@ -2127,3 +2127,80 @@ def test_import_of_private_symbols(tmp_path, frontend): assert var.type.imported is True # Check if the symbol comes from the mod_public module assert var.type.module is mod_public + + +@pytest.mark.parametrize('frontend', available_frontends( + xfail=[(OMNI, 'OMNI does not like intrinsic shading for member functions!')] +)) +def test_intrinsic_shadowing(tmp_path, frontend): + """ + Test that locally defined functions that shadow intrinsics are handled. + """ + fcode_algebra = """ +module algebra_mod +implicit none +contains + function dot_product(a, b) result(c) + real(kind=8), intent(inout) :: a(:), b(:) + real(kind=8) :: c + end function dot_product + + function min(x, y) + real(kind=8), intent(in) :: x, y + real(kind=8) :: min + + min = y + if (x < y) min = x + end function min +end module algebra_mod +""" + + fcode = """ +module test_intrinsics_mod +use algebra_mod, only: dot_product +implicit none + +contains + + subroutine test_intrinsics(a, b, c, d) + use algebra_mod, only: min + implicit none + real(kind=8), intent(inout) :: a(:), b(:) + real(kind=8) :: c, d, e + + c = dot_product(a, b) + d = max(c, a(1)) + e = min(c, a(1)) + + contains + + function max(x, y) + real(kind=8), intent(in) :: x, y + real(kind=8) :: max + + max = y + if (x > y) max = x + end function max + end subroutine test_intrinsics +end module test_intrinsics_mod +""" + algebra = Module.from_source(fcode_algebra, frontend=frontend, xmods=[tmp_path]) + module = Module.from_source( + fcode, definitions=algebra, frontend=frontend, xmods=[tmp_path] + ) + routine = module['test_intrinsics'] + + assigns = FindNodes(ir.Assignment).visit(routine.body) + assert len(assigns) == 3 + + assert isinstance(assigns[0].rhs.function, sym.ProcedureSymbol) + assert not assigns[0].rhs.function.type.is_intrinsic + assert assigns[0].rhs.function.type.dtype.procedure == algebra['dot_product'] + + assert isinstance(assigns[1].rhs.function, sym.ProcedureSymbol) + assert not assigns[1].rhs.function.type.is_intrinsic + assert assigns[1].rhs.function.type.dtype.procedure == routine.members[0] + + assert isinstance(assigns[2].rhs.function, sym.ProcedureSymbol) + assert not assigns[2].rhs.function.type.is_intrinsic + assert assigns[2].rhs.function.type.dtype.procedure == algebra['min'] diff --git a/loki/ir/tests/test_visitor.py b/loki/ir/tests/test_visitor.py index c5a5a2e3a..f0056a406 100644 --- a/loki/ir/tests/test_visitor.py +++ b/loki/ir/tests/test_visitor.py @@ -369,7 +369,7 @@ def test_find_variables_associates(frontend): routine = Subroutine.from_source(fcode, frontend=frontend) variables = FindVariables(unique=False).visit(routine.body) - assert len(variables) == 29 + assert len(variables) == 27 if frontend == OMNI else 28 assert len([v for v in variables if v.name == 'v']) == 1 assert len([v for v in variables if v.name == 'm']) == 2 diff --git a/loki/tests/test_modules.py b/loki/tests/test_modules.py index 52dce2dc6..7914fd1cf 100644 --- a/loki/tests/test_modules.py +++ b/loki/tests/test_modules.py @@ -1330,11 +1330,11 @@ def test_module_enrichment_within_file(frontend, tmp_path): integer, parameter :: j = 16 contains - integer function SUM(v) + integer function plus_one(v) implicit none integer, intent(in) :: v - SUM = v + 1 - end function SUM + plus_one = v + 1 + end function plus_one end module foo module test @@ -1348,7 +1348,7 @@ def test_module_enrichment_within_file(frontend, tmp_path): real(kind=rk), intent(inout) :: res integer(kind=ik) :: i do i = 1, n - res = res + SUM(j) + res = res + plus_one(j) end do end subroutine calc end module test @@ -1358,10 +1358,10 @@ def test_module_enrichment_within_file(frontend, tmp_path): routine = source['calc'] calls = list(FindInlineCalls().visit(routine.body)) assert len(calls) == 1 - assert calls[0].function == 'sum' + assert calls[0].function == 'plus_one' assert calls[0].function.type.imported assert calls[0].function.type.module is source['foo'] - assert calls[0].function.type.dtype.procedure is source['sum'] + assert calls[0].function.type.dtype.procedure is source['plus_one'] if frontend != OMNI: # OMNI inlines parameters assert calls[0].arguments[0].type.dtype == BasicType.INTEGER diff --git a/loki/transformations/tests/test_raw_stack_allocator.py b/loki/transformations/tests/test_raw_stack_allocator.py index 49e6f2346..d833f8d33 100644 --- a/loki/transformations/tests/test_raw_stack_allocator.py +++ b/loki/transformations/tests/test_raw_stack_allocator.py @@ -10,7 +10,7 @@ from loki.backend import fgen from loki.batch import Scheduler, SchedulerConfig from loki.dimension import Dimension -from loki.expression import DeferredTypeSymbol, InlineCall, IntLiteral +from loki.expression import DeferredTypeSymbol, InlineCall, IntLiteral, ProcedureSymbol from loki.frontend import available_frontends, OMNI from loki.ir import FindNodes, CallStatement, Assignment, Pragma from loki.sourcefile import Sourcefile @@ -282,7 +282,7 @@ def test_raw_stack_allocator_temporaries(frontend, block_dim, horizontal, direct real = BasicType.REAL logical = BasicType.LOGICAL jprb = DeferredTypeSymbol('JPRB') - srk = InlineCall(function = DeferredTypeSymbol(name = 'SELECTED_REAL_KIND'), + srk = InlineCall(function = ProcedureSymbol(name = 'SELECTED_REAL_KIND'), parameters = (IntLiteral(13), IntLiteral(300))) stack_dict = kernel1_item.trafo_data[transformation._key]['stack_dict'] diff --git a/loki/types.py b/loki/types.py index bf79766dc..73f80b6f6 100644 --- a/loki/types.py +++ b/loki/types.py @@ -168,16 +168,22 @@ class ProcedureType(DataType): Indicate that this is a function is_generic : bool, optional Indicate that this is a generic function + is_intrinsic : bool, optional + Indicate that this is an intrinsic function procedure : :any:`Subroutine` or :any:`StatementFunction` or :any:`LazyNodeLookup`, optional The procedure this type represents """ - def __init__(self, name=None, is_function=None, is_generic=False, procedure=None, return_type=None): + def __init__( + self, name=None, is_function=None, is_generic=False, + is_intrinsic=False, procedure=None, return_type=None + ): from loki.subroutine import Subroutine # pylint: disable=import-outside-toplevel,cyclic-import super().__init__() assert name or isinstance(procedure, Subroutine) - assert isinstance(return_type, SymbolAttributes) or procedure or not is_function + assert isinstance(return_type, SymbolAttributes) or procedure or not is_function or is_intrinsic self.is_generic = is_generic + self.is_intrinsic = is_intrinsic if procedure is None or isinstance(procedure, LazyNodeLookup): self._procedure = procedure self._name = name diff --git a/pyproject.toml b/pyproject.toml index 9ce538ce1..caac0c9c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "coloredlogs", # optional for loki-build utility "junit_xml", # optional for JunitXML output in loki-lint "codetiming", # essential for scheduler and sourcefile timings - "pydantic>=2.0", # type checking for IR nodes + "pydantic>=2.0,<2.10.0", # type checking for IR nodes ] [project.optional-dependencies]