# Resistance to modernization

**URL:** <https://fortran-lang.discourse.group/t/resistance-to-modernization/4136>\
**Category:** Uncategorized\
**Created:** [August 8, 2022, 8:23pm UTC](https://fortran-lang.discourse.group/t/resistance-to-modernization/4136 "2022-08-08T20:23:21Z")\
**Posts on this page:** 1\
**Showing post:** 6

<div class="post-metadata">

**Author:** ![ivanpribec](https://yyz2.discourse-cdn.com/free1/user_avatar/fortran-lang.discourse.group/ivanpribec/32/3290_2.png) [@ivanpribec](https://fortran-lang.discourse.group/u/ivanpribec)\
**Post date:** [August 8, 2022, 10:17pm UTC](https://fortran-lang.discourse.group/t/resistance-to-modernization/4136/6 "2022-08-08T22:17:05Z")

</div>

As a sidenote, the motivation for this thread came from refactoring the LAPACK function [`lsame`](https://github.com/Reference-LAPACK/lapack/issues/701). The Reference LAPACK version is cluttered by additional else-if branches for processors that use other encodings than ASCII such as IBM mainframes using EBCDIC or the Prime OS @MarDie posted about.

`lsame` is really just a “trivial” support routine used to compare two characters. It is used in the routines which can perform operations on non-transpose, transpose or conjugate forms of matrices, i.e. `'N'`, `'T'`, to check which character was provided at the call site and perform the corresponding operation.

The original and F95 code are given in the drop-down arrows below. Observe that gfortran generates the same assembly code when using a reasonable optimization flag. Based on the character encoding it infers from the statement `ZCODE = ichar('Z')` it can simply eliminate the dead branches.

> **Original**
>
> ```fortran
> LOGICAL FUNCTION LSAME( CA, CB )
> *
> * -- LAPACK auxiliary routine --
> * -- LAPACK is a software package provided by Univ. of Tennessee, --
> * -- Univ. of California Berkeley, Univ. of Colorado Denver and NAG Ltd..--
> *
> * .. Scalar Arguments ..
> CHARACTER CA, CB
> * ..
> *
> * =====================================================================
> *
> * .. Intrinsic Functions ..
> INTRINSIC ICHAR
> * ..
> * .. Local Scalars ..
> INTEGER INTA, INTB, ZCODE
> * ..
> * .. Executable Statements ..
> *
> * Test if the characters are equal
> *
> LSAME = CA.EQ.CB
> IF( LSAME )
> $ RETURN
> *
> * Now test for equivalence if both characters are alphabetic.
> *
> ZCODE = ICHAR( 'Z' )
> *
> * Use 'Z' rather than 'A' so that ASCII can be detected on Prime
> * machines, on which ICHAR returns a value with bit 8 set.
> * ICHAR('A') on Prime machines returns 193 which is the same as
> * ICHAR('A') on an EBCDIC machine.
> *
> INTA = ICHAR( CA )
> INTB = ICHAR( CB )
> *
> IF( ZCODE.EQ.90 .OR. ZCODE.EQ.122 ) THEN
> *
> * ASCII is assumed - ZCODE is the ASCII code of either lower or
> * upper case 'Z'.
> *
> IF( INTA.GE.97 .AND. INTA.LE.122 ) INTA = INTA - 32
> IF( INTB.GE.97 .AND. INTB.LE.122 ) INTB = INTB - 32
> *
> ELSE IF( ZCODE.EQ.233 .OR. ZCODE.EQ.169 ) THEN
> *
> * EBCDIC is assumed - ZCODE is the EBCDIC code of either lower or
> * upper case 'Z'.
> *
> IF( INTA.GE.129 .AND. INTA.LE.137 .OR.
> $ INTA.GE.145 .AND. INTA.LE.153 .OR.
> $ INTA.GE.162 .AND. INTA.LE.169 ) INTA = INTA + 64
> IF( INTB.GE.129 .AND. INTB.LE.137 .OR.
> $ INTB.GE.145 .AND. INTB.LE.153 .OR.
> $ INTB.GE.162 .AND. INTB.LE.169 ) INTB = INTB + 64
> *
> ELSE IF( ZCODE.EQ.218 .OR. ZCODE.EQ.250 ) THEN
> *
> * ASCII is assumed, on Prime machines - ZCODE is the ASCII code
> * plus 128 of either lower or upper case 'Z'.
> *
> IF( INTA.GE.225 .AND. INTA.LE.250 ) INTA = INTA - 32
> IF( INTB.GE.225 .AND. INTB.LE.250 ) INTB = INTB - 32
> END IF
> LSAME = INTA.EQ.INTB
> *
> * RETURN
> *
> * End of LSAME
> *
> END
> 
> ```
> 
> Output from compiler explorer, with [`gfortran-12 -ffixed-form -O2`](https://godbolt.org/z/Pxj8vdbP8)
> 
> ```nohighlight
> lsame_:
> movzx eax, BYTE PTR [rdi]
> movzx edx, BYTE PTR [rsi]
> mov ecx, 1
> cmp al, dl
> je .L1
> lea esi, [rax-97]
> lea ecx, [rax-32]
> cmp esi, 26
> lea esi, [rdx-97]
> cmovb eax, ecx
> cmp esi, 26
> lea ecx, [rdx-32]
> cmovb edx, ecx
> xor ecx, ecx
> cmp eax, edx
> sete cl
> .L1:
> mov eax, ecx
> ret
> 
> ```

> **Refactored**
>
> ```fortran
> pure logical function lsame( ca, cb )
> character(len=1), intent(in) :: ca, cb
> 
> integer, parameter :: diff_case = iachar('a')-iachar('A')
> integer :: ia, ib
> 
> lsame = ca == cb
> if (lsame) return 
> 
> ! case-insensitive test
> ia = iachar(ca)
> ib = iachar(cb)
> 
> ! Capital alphabetic characters are between 65 and 90
> ! in the ASCII character encoding
> if ( 64 < ia .and. ia < 91) ia = ia + diff_case
> if ( 64 < ib .and. ib < 91) ib = ib + diff_case
> 
> lsame = ia == ib
> 
> end
> 
> ```
> 
> Output from compiler explorer, with [`gfortran-12 -ffixed-form -f95 -O2`](https://godbolt.org/z/5EaK5Yeaz)
> 
> ```nohighlight
> lsame_:
> movzx eax, BYTE PTR [rdi]
> movzx edx, BYTE PTR [rsi]
> mov ecx, 1
> cmp al, dl
> je .L1
> lea esi, [rax-65]
> lea ecx, [rax+32]
> cmp esi, 26
> lea esi, [rdx-65]
> cmovb eax, ecx
> cmp esi, 26
> lea ecx, [rdx+32]
> cmovb edx, ecx
> xor ecx, ecx
> cmp eax, edx
> sete cl
> .L1:
> mov eax, ecx
> ret
> 
> ```

From a functional point of view, you could say the refactoring brings little to no change. However the impact on readability and intent is non-negligible IMO.

Amusingly, the C interfaces to this function (which from a modern Fortran perspective shouldn’t even be part of the public interface) are different:

- [Intel MKL:](https://www.intel.com/content/www/us/en/develop/documentation/onemkl-developer-reference-c/top/support-functions/character-equality-testing/lsame.html) `int lsame( const char* ca, const char* cb, int lca, int lcb );`
- [BLIS](https://github.com/flame/blis/blob/545e6c2f6d09d023b353002a9a43b11aa0c1d701/frame/compat/f2c/bla_lsame.c): `int PASTEF770(lsame)(const char *ca, const char *cb, int ca_len, int cb_len)`
- [OpenBLAS](https://github.com/xianyi/OpenBLAS/blob/develop/kernel/generic/lsame.c): `int NAME(char *A, char *B){`
- [Reference LAPACK:](https://github.com/Reference-LAPACK/lapack/blob/master/LAPACKE/include/lapack.h#L117) `lapack_logical LAPACK_lsame_base( char* ca, char* cb, lapack_int lca, lapack_int lcb`

OpenBLAS also maintains several assembly specializations for [DEC Alpha](https://github.com/xianyi/OpenBLAS/blob/develop/kernel/alpha/lsame.S), [ia64](https://github.com/xianyi/OpenBLAS/blob/develop/kernel/ia64/lsame.S), [power](https://github.com/xianyi/OpenBLAS/blob/develop/kernel/power/lsame.S), [sparc](https://github.com/xianyi/OpenBLAS/blob/develop/kernel/sparc/lsame.S) and [x86](https://github.com/xianyi/OpenBLAS/blob/develop/kernel/x86/lsame.S). Btw, OpenBLAS also seems to have kicked out EBCDIC and Prime OS. Now, to add further to my confusion as a Fortran programmer, in C you also need a version which can accept literals by value:

```c
lapack_logical LAPACKE_lsame( char ca, char cb )
{
    return (lapack_logical) LAPACK_lsame( &ca, &cb, 1, 1 );
}

```

Since the function is used a lot in LAPACK, it tripped up the GCC static analyzer, leading the developers to add [` __attribute__ ((const))`](https://github.com/xianyi/OpenBLAS/issues/3543), which is kind of similar to what `simple` procedures will provide in Fortran 202x but without requiring a preprocessor.

**TL;DR** Doing a case-insensitive comparison led to a big language mess.

It makes me afraid of what idiosyncrasies/baggage can be found in the rest of the interfaces.

---

_[View the full topic](https://fortran-lang.discourse.group/t/resistance-to-modernization/4136)._
