unlock unlimited code runs for $79, limited time →
NOSSLETTER.techLIVE
back to OSSpedia
api-designrefactoringtesting-strategy

[Flang] Interface vs explicit intrinsics priority

Project: llvm/llvm-project | Language: C++ | Concepts: api-design, refactoring, testing-strategy | Difficulty: mid
Link: https://github.com/llvm/llvm-project/pull/215355

The problem

In Fortran, when both an explicit intrinsic and a user-defined interface (i.e., a specific procedure) are available for a generic name (like sin), the compiler must choose which one to use. The previous implementation in Flang incorrectly prioritized explicit intrinsics over user-defined interface specifics, leading to the wrong procedure being called in some cases.

Why it's non-trivial

A naive fix might simply reorder the checks, but getting the resolution order correct requires careful handling of all possible combinations (elemental, non-elemental, intrinsic, etc.), and ensuring that all branches of the logic are covered and tested. The Fortran standard's rules are subtle, and missing a case could introduce subtle bugs or regressions.

The approach

The core change is in ExpressionAnalyzer::ResolveGeneric. The logic that determines which procedure to resolve for a generic name was updated to give priority to user-defined specifics (both non-elemental and elemental) over explicit intrinsics.

Key code snippets:

  1. Remove the check that skipped user-defined specifics if an explicit intrinsic was present:

    -  if (genericDetails && !explicitIntrinsic) {
    +  if (genericDetails) {
    

    Annotation:
    Previously, if an explicit intrinsic was present, the code would skip looking for user-defined specifics. The new code always checks for specifics if they exist, regardless of whether an explicit intrinsic is present.

  2. Reorder the resolution priority:

    -  if (explicitIntrinsic) {
    -    return {explicitIntrinsic, false};
    -  } else if (nonElemental) {
    +  if (nonElemental) {
         return {&AccessSpecific(symbol, *nonElemental), false};
       } else if (elemental) {
         return {&AccessSpecific(symbol, *elemental), false};
    +  } else if (explicitIntrinsic) {
    +    return {explicitIntrinsic, false};
       }
    

    Annotation:
    The new order is:

    1. Non-elemental user-defined specific
    2. Elemental user-defined specific
    3. Explicit intrinsic
      This matches the Fortran standard's intended resolution order.
  3. Expanded test coverage:

    A new test file, symbol34.f90, was added to verify the new resolution order in various scenarios:

    !DEF: /MAIN/sin ELEMENTAL, INTRINSIC, PURE (Function) Generic
    intrinsic :: sin
    interface sin
        procedure :: internal_definition_sin_real4
    end interface
    ...
    a = sin(1.0)  ! Should call internal_definition_sin_real4, not the intrinsic
    b = sin(1.0_8)  ! Should fall back to intrinsic for kind=8
    

    Annotation:
    The test ensures that user-defined specifics are chosen over intrinsics when available, and that the fallback to the intrinsic works when no matching specific exists.

From the review thread

LGTM overall.

One ask before I approve — test coverage. symbol34.f90 currently pins the nonelemental-function case and the kind-8 intrinsic fallthrough, but not the other resolution legs this PR changes. Could you extend it with these cases?

An elemental specific beating the intrinsic (15.5.5.2 step (2) ahead of step (3)):

program p
  intrinsic :: sin
  interface sin
    procedure :: mysin
  end interface
  print *, sin(1.0)   ! must call mysin (prints 101.0), not the intrinsic
contains
  elemental real function mysin(x)
    real, intent(in) :: x
    mysin = x + 100.
  end function
end program

A subroutine generic — the same rules apply to subroutines, and no in-tree test pins that leg today:

program p
  intrinsic :: cpu_time
  interface cpu_time
    procedure :: mytime
  end inte

Takeaways

  • Always follow language specification precedence rules when resolving overloaded or generic symbols, especially when both user-defined and intrinsic options exist.
  • Comprehensive test coverage should include all resolution branches, not just the most common or obvious cases.
  • When changing symbol resolution logic, ensure that fallback behavior (e.g., to intrinsics when no specific matches) is still correct and tested.