fortran-lang / stdlib

Fortran Standard Library
https://stdlib.fortran-lang.org
MIT License
1.09k stars 168 forks source link

'Sleep' not working when compiled in Win32 (x86) #738

Open davidpfister opened 1 year ago

davidpfister commented 1 year ago

Description

The Sleep function does not even compile on Windows using ifort (ia-32 2021.5.0). One gets fatal error LNK1120: 1 unresolved external error LNK2019: unresolved external symbol _Sleep

It works fine when building in x64 though.

Expected Behaviour

The code should compile for x86 and x64 platforms.

Version of stdlib

v0.3.0

Platform and Architecture

Windows

Additional Information

After playing around I came up with a solution that works for me. In x86 the sleep function on Windows should be called Sleep@4.

I changed the code as follows

#ifdef _WIN32
#ifdef _WIN64
        subroutine  winsleep(dwMilliseconds) bind (C, name='Sleep')
            import :: DWORD
            integer(DWORD) :: dwMilliseconds
        end subroutine
#else
        subroutine  winsleep(dwMilliseconds) bind (C, name='Sleep@4')
            import :: DWORD
            integer(DWORD) :: dwMilliseconds
        end subroutine
#endif
#else
        integer(c_int) function usleep(usec) bind (C)
            import c_int
            integer(c_int), value, intent(in) :: usec
        end function
#endif

subroutine sleep(millisec)
        integer, intent(in) :: millisec
        integer(c_int) :: ierr

#ifdef _WIN32
        call winsleep(%val(transfer(millisec, DWORD)))
#else
        ierr = usleep(int(millisec * 1000, c_int))
        if (ierr/=0) error stop 'problem with usleep() system call'
#endif
    end subroutine

and the whole thing works like a charm

jvdp1 commented 10 months ago

Thank you for reporting the bug. Should this solution be for Intel ifort on Windows ia-32? Or is it a more general solution for all compilers on Windows ia-32? (My experience with Window is close to 0:()

davidpfister commented 10 months ago

@jvdp1 Good question. It might be an ifort specificity on Windows. I do not have access to other compilers at the moment. I may be able to give it a try with gfortran and keep you posted. As far as I know the pragma _WIN32/_WIN64 are only standard for ifort (maybe ifx) on Windows, so the proposed code would only work with ifort anyway.

davidpfister commented 10 months ago

After playing around with different function names I figured out that one should call _sleep. By doing so, there is no different between x86 and x64

#ifdef _WIN32
        subroutine  winsleep(dwMilliseconds) bind (C, name='_sleep')
            import :: DWORD
            integer(DWORD) :: dwMilliseconds
        end subroutine
#else
        integer(c_int) function usleep(usec) bind (C)
            import c_int
            integer(c_int), value, intent(in) :: usec
        end function
#endif

(and I specified use, intrinsic :: iso_c_binding, only : DWORD => c_long)

davidpfister commented 9 months ago

I finally found an answer to whether or not the @4 is Intel specific or not (I am quoting S. Lionel here):

The problem is bigger than that. All Win32 API routines on 32-bit Windows use the STDCALL calling mechanism, which has a naming convention adding the @n at the end of the name to signify the number of bytes to pop off the stack on return. gfortran, like most Fortran compilers on Windows (except for CVF and MSFPS) default to the C convention. If you call a STDCALL routine but the compiler thinks it is a C routine, the stack gets popped twice and you corrupt the stack. Things go downhill from there. You will have to see what gfortran offers for specifying that an external routine is STDCALL. Whatever it is, it's going to be an extension and probably not part of BIND. In Intel Fortran, for example, one uses !DEC$ ATTRIBUTES STDCALL :: routinename Never, ever attempt to paper over the naming convention difference by simply adding the @n suffix - the errors this will cause can be mystifying. Steve

But it looks like bad practice anyway 🤔

jvdp1 commented 9 months ago

Thank you @davidpfister for this explanation. However, it is still not clear to me what we should do to solve this issue. What would you suggest as the best way to solve this issue?

davidpfister commented 9 months ago

Hi @jvdp1, so if I summarize, we have:

IMHO, the last option should be the way to go.

jvdp1 commented 9 months ago

Thank you @davidpfister for the summary.

IMHO, the last option should be the way to go.

If I am right, these two directives are only for intel and GCC compilers. So, I am a bit afraid that this solution will not work with other compilers. Is it right?

davidpfister commented 9 months ago

It's true that it is not standard compliant. $DEC is supported by ifort (and probably ifx). If is also supported by gfortran with the flag -fdec. According to the user manual, $DEC is also supported by pgi, and absoft uses $DIR. Not sure about the other compilers like flang, lfortran, lahay, XL.

Romendakil commented 9 months ago

I would absolutely refrain from using $DEC statements, and advocate to go for a C wrapper.

PierUgit commented 5 months ago

so if I summarize, we have:

* the approach with the @4, which is discouraged.

* the possibility to bind the deprecated '_sleep'.

If I understand correctly _sleep is deprecated but needed only for 32 bits builds. Sounds perfectly reasonable to me to continue using it, as building new 32 bits applications on Windows is itself obsolete. There is no 32 bits version of W11, and executing 32 bits applications is kept for backward compatility only.

davidpfister commented 5 months ago

Yep, _sleep is obsolete since 2015, but, as of today, it is still part of the CRT. So what you are suggesting @PierUgit would work. And indeed, Win32 is a dinosaure that I still like to keep around for compatibility reasons.
Provided that the macro _WIN32 and _WIN64 are defined (by default in ifort), you end up with something like this

#ifdef _WIN32
#ifdef _WIN64
        subroutine  winsleep(dwMilliseconds) bind (C, name='Sleep')
            import :: DWORD
            integer(DWORD) :: dwMilliseconds
        end subroutine
#else
        subroutine  winsleep(dwMilliseconds) bind (C, name='_sleep')
            import :: DWORD
            integer(DWORD) :: dwMilliseconds
        end subroutine
#endif
#else

It has been tested on W10 with ifort 2020 and it works as expected in Win32 and x64.