Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
davidpfister opened this issue Sep 15, 2023 · 11 comments
Open

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

davidpfister opened this issue Sep 15, 2023 · 11 comments
Labels
bug Something isn't working

Comments

@davidpfister
Copy link

davidpfister commented Sep 15, 2023

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

@davidpfister davidpfister added the bug Something isn't working label Sep 15, 2023
@jvdp1
Copy link
Member

jvdp1 commented Dec 25, 2023

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
Copy link
Author

@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
Copy link
Author

davidpfister commented Jan 4, 2024

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
Copy link
Author

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
Copy link
Member

jvdp1 commented Feb 4, 2024

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
Copy link
Author

davidpfister commented Feb 5, 2024

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

  • the approach with the @4, which is discouraged.
  • the possibility to bind the deprecated '_sleep'.
  • the possibility to create a small C wrapper
  • or finally, had the STDCALL directives to the interface
subroutine winsleep(dwMilliseconds) bind(C, name='Sleep')
	!DEC$ ATTRIBUTES STDCALL :: Sleep
	!GCC$ ATTRIBUTES STDCALL :: Sleep
        import :: DWORD
        integer(DWORD), value :: dwMilliseconds
end subroutine

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

@jvdp1
Copy link
Member

jvdp1 commented Feb 8, 2024

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
Copy link
Author

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
Copy link

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

@PierUgit
Copy link
Contributor

PierUgit commented May 28, 2024

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
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

4 participants