Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

setjmp/longjmp x64

netphilou31
New Contributor II
3,328 Views

Hi,

I'm working on old code that was originally only built as a 32-bit version. This code uses the setjmp/longjmp routines to easily break out of a location that may be deep inside the call sequence. Originally a simple STOP statement was used, but when the code was turned into a DLL, it was the easiest way to quit the DLL. It works perfectly in a 32-bit setup, but when I try to test this feature with a 64-bit DLL, I couldn't get it to work. Every time I try to call the longjmp routine an exception is thrown somewhere in ntdll.dll. The jmp_buf object seems to match the one defined in the setjmp.h source file, which is different between the 32-bit and 64-bit versions.

Is there something more to do for 64-bit or it just doesn't work in 64-bit?

Regards,

 

P.S.: Here is my source code:

    use IFWIN, only: UINT64

!dec$ if defined(_X86_)
    integer(4), public :: JMP_BUF(16)
!dec$ else
    type SETJMP_FLOAT128
        sequence
        integer(UINT64) Part(2)
    end type SETJMP_FLOAT128
    type(SETJMP_FLOAT128), public :: JMP_BUF(16)
!dec$ endif

!dec$ if defined(_X86_)
    interface
        integer(4) function SETJMP(JMP_BUF)
        !dec$ attributes C, alias:'__setjmp' :: SETJMP
            integer(4) :: JMP_BUF(*)
        end function
    end interface
    interface
        subroutine LONGJMP(JMP_BUF, RET_VALUE)
        !dec$ attributes C :: LONGJMP
            integer(4) :: JMP_BUF(*)
            integer(4) :: RET_VALUE
        end subroutine
    end interface
!dec$ else
    interface
        integer(4) function SETJMP(JMP_BUF)
        !dec$ attributes C :: SETJMP
            import
            type(SETJMP_FLOAT128) :: JMP_BUF(*)
        end function
    end interface
    interface
        subroutine LONGJMP(JMP_BUF, RET_VALUE)
        !dec$ attributes C :: LONGJMP
            import
            type(SETJMP_FLOAT128) :: JMP_BUF(*)
            integer(4)            :: RET_VALUE
        end subroutine
    end interface
!dec$ endif

 

0 Kudos
25 Replies
jimdempseyatthecove
Honored Contributor III
2,888 Views

Not seeing the actual code for SETJMP and LONGJMP, we can only make assumptions.

Purely guessing here... 64-bit systems may require:

...
integer(8) :: JMP_BUF(*)
integer(4) :: RET_VALUE
...

***

However, I also suspect the code that implements SETJMP and LONGJMP is written in C...

This leads me to presume that you are linking in (or building) the C code (on 64-bit platform) to save the stack/calling args as if on 32-bit machine (e.g. saving a 32-bit address as opposed to the full 64-bit address).

Jim Dempsey

0 Kudos
netphilou31
New Contributor II
2,882 Views

Hi Jim,

Thanks for your reply.

The setjmp/longjmp routines are those from VC runtime library, the map file refers to:

  • libvcruntime:setjmp.obj
  • libvcruntime:longjmpc.obj

So, I cannot provide you more information.

Anyway, I have also tried to replace the original declaration

 

type(SETJMP_FLOAT128) :: JMP_BUF(*)

 

by 

 

integer(UNIT64) :: JMP_BUF(*)

 

Without more success.

Not sure if it can help but I can join the setjmp.h file from where I found the information.

Best regards,

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,864 Views

I suspect this is a calling convention issue. The simplest way to resolve this (other than blindly playing with conventions) is to write C wrapper functions that you call from Fortran. Example

Fortran
interface
function my_setjmp() result(ret) bind(C)
  integer :: ret
end function my_setjmp
subroutine my_longjmp(ret) bind(C)
  integer, value :: ret
end subroutine my_longjmp
end interface
...
ret = my_setjmp()
if(ret/=0) then
  ... ! exit code using ret
endif
... ! main code
call my_longjmp(your_error_code) ! e
// C code
#include <stdio.h>
#include <setjmp.h>
static jmp_buf buf;
int my_setjmp()
{
  return setjmp(buf);
}
void my_longjmp(int ret)
{
  longjmp(buf, ret);
}

Jim Dempsey

0 Kudos
netphilou31
New Contributor II
2,825 Views

Thanks for the idea, but on my side, I use only Fortran coding, the calling process is an application written in Delphi; application which must get a return condition from the dll and I'm not sure I want to add an intermediate layer in C, but why not?

What looks strange to me is that it works in 32-bit, so I guess that the calling convention is correct.

Phil.

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,744 Views

>>so I guess that the calling convention is correct.

There are multiple calling conventions for both 32-bit and 64-bit environments. 

The fact that 32-bit works may be by design or by chance....

The fact the 64-bit fails is indicative that your calling convention is incorrect.

Try this:

interface
function setjmp(buf) result(ret) bind(C)
  integer(1) :: buff(16) ! 16 byte opaque buffer
  integer :: ret
end function setjmp
subroutine longjmp(buf, ret) bind(C)
  integer(1) :: buff(16) ! 16 byte opaque buffer
  integer :: ret
end subroutine longjmp
end interface

The above is but a guess.

Jim Dempsey

 

0 Kudos
netphilou31
New Contributor II
2,669 Views

I have tried something close to what you are proposing, but since it is "C binded", the return value must be a C_PTR type which must be converted to retrieve the correct value. Unfortunately, this work in 32-bit but not in 64-bit; I am getting the same issue.

Here is the error message I get while debugging (I am sorry, but it is in French).

Unhandled exception at 0x00007FF9932D0BA4 (ntdll.dll) in myApp.exe: 0xC000041D: Une exception non gérée a été détectée pendant un rappel de l’utilisateur.

Which google translate as: Unhandled exception at 0x00007FF9932D0BA4 (ntdll.dll) in myApp.exe: 0xC000041D: An unhandled exception was caught during a user callback.

Not if it helps, but here are the new interfaces:

    interface
        function SETJMP(JMP_BUF) result(ret_val) bind(C)
            use ISO_C_BINDING
            import
!dec$ if defined(_X86_)
            integer(4)      :: JMP_BUF(*)
!dec$ else
            integer(UINT64) :: JMP_BUF(*)
!dec$ endif
            type(C_PTR)     :: ret_val
        end function
    end interface
    interface
        subroutine LONGJMP(JMP_BUF, RET_VALUE) bind(C)
!dec$ if defined(_X86_)
            integer(4) :: JMP_BUF(*)
!dec$ else
            integer(8) :: JMP_BUF(*)
!dec$ endif
            integer(4) :: RET_VALUE
        end subroutine
    end interface

and the call to setjmp:

      use ISO_C_BINDING
C
      .../...
C
      type(C_PTR) lpIErr
      integer(4), pointer :: rcc
C
      .../...
C
      lpIErr = SETJMP(JMP_BUF)
C
      call C_F_POINTER(CPTR=lpIErr, FPTR=rcc, SHAPE=[1])
C
      if (c_associated(lpIErr)) IErr = rcc
C
      if (IErr == 0) then
          .../...
      end if
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,602 Views

From the C documentation, the return value is int (integer(4)). This is the return error code as typically returned by C's main.

The buffer must be large enough to hold the PC and SP (8 bytes each on 64-bit, 4 bytes each on 32-bit). The Linux docs use a blob of FLOAT128 (16 bytes). The type is immaterial as this is an opaque object. The size must be at least the sizeof(PC)+sizeof(SP).

What did you set aside for JMP_BUF?

 

Assuming your JMP_BUF is large enough, I'd suggest writing a simple piece of test code and stepping through it in Disassembly mode. You should be able to make sense out of it.

 

Note, if the C code works as illustrated earlier, just use it and get on with your work. 

 

Jim Dempsey

0 Kudos
netphilou31
New Contributor II
2,472 Views

I agree that the return value from setjmp should be int (integer(4)) but adding the bind(C) attribute changed the type of the returned value. In 32-bit, before adding bind(C) the value returned from the call to longjmp was -4 (as I expected), but after adding bind(C) the value returned looked more like a pointer value, so I assumed it was a C_PTR type and that's why I added the C_F_POINTER conversion to retrieve the desired value of -4 and it worked in 32-bit.

Now going back to your question about the size of the JMP_BUF object, it's declared as integer (4) with size 16 for 32-bit (which actually works) and integer (8) with size 32 for 64-bit building. Since that doesn't work, I tried increasing its size to 100 with no more success.

I tried exiting the first called routine from my main routine using a longjmp() call before anything else was done (the main routine contains only common declarations and file management things). As expected, it worked in 32-bit but issued the same error message in 64-bit.

Cordially,

Phil.

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,414 Views

I will investigate this here with a simple reproducer.

 

Jim

 

0 Kudos
netphilou31
New Contributor II
2,410 Views
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,407 Views

Here is the simple reproducer that works in 32-bit

*** and fails in 64-bit as you've observed.

My suspicion is a bug in the generated code by the compiler (or longjmp implementation).

The areas of code that I suspect are at issue relate to the subroutine/function return cleanup, the section of code that deletes local allocatable variables that have not been expressly deleted. In tracing the disassembly code of longjmp of this simple program (with no allocatables) it appears to nest deeper and deeper into the code although at one point the correct return values are in three registers but a subsequent test bypasses the code to return and instead nests deeper.

!  LongJump.f90 
program LongJump
    implicit none
    integer(1) :: buf(16) ! 16 byte opaque buffer
    integer :: ret
    
    interface
    function setjmp(buf) result(ret) bind(C)
      integer(1) :: buf(*) ! opaque buffer
      integer :: ret
    end function setjmp
    subroutine longjmp(buf, ret) bind(C)
      integer(1) :: buf(*) ! opaque buffer
      integer, value :: ret
    end subroutine longjmp
    end interface

    ret = setjmp(buf)
    if(ret==0) then
        print *,"start of program"
        ! ... main code
        call longjmp(buf, 9) ! abort with error code 9
        stop "Shouldn't reach here"
    endif
    print *,"return from longjmp with ret=",ret
end program LongJump

Jim Dempsey

0 Kudos
MWind2
New Contributor III
2,379 Views
//
// setjmp.h
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The C Standard Library <setjmp.h> header.
//
#pragma once
#define _INC_SETJMP

#include <vcruntime.h>

#ifdef _M_CEE
// The reason why simple setjmp won't work here is that there may
// be case when CLR stubs are on the stack e.g. function call just
// after jitting, and not unwinding CLR will result in bad state of
// CLR which then can AV or do something very bad.
#include <setjmpex.h>
#endif

#pragma warning(push)
#pragma warning(disable: _VCRUNTIME_DISABLED_WARNINGS)

_CRT_BEGIN_C_HEADER



// Definitions specific to particular setjmp implementations.
#if defined _M_IX86

#define _JBLEN 16
#define _JBTYPE int

typedef struct __JUMP_BUFFER
{
unsigned long Ebp;
unsigned long Ebx;
unsigned long Edi;
unsigned long Esi;
unsigned long Esp;
unsigned long Eip;
unsigned long Registration;
unsigned long TryLevel;
unsigned long Cookie;
unsigned long UnwindFunc;
unsigned long UnwindData[6];
} _JUMP_BUFFER;

#elif defined _M_X64

typedef struct _VCRT_ALIGN(16) _SETJMP_FLOAT128
{
unsigned __int64 Part[2];
} SETJMP_FLOAT128;

#define _JBLEN 16
typedef SETJMP_FLOAT128 _JBTYPE;

typedef struct _JUMP_BUFFER
{
unsigned __int64 Frame;
unsigned __int64 Rbx;
unsigned __int64 Rsp;
unsigned __int64 Rbp;
unsigned __int64 Rsi;
unsigned __int64 Rdi;
unsigned __int64 R12;
unsigned __int64 R13;
unsigned __int64 R14;
unsigned __int64 R15;
unsigned __int64 Rip;
unsigned long MxCsr;
unsigned short FpCsr;
unsigned short Spare;

SETJMP_FLOAT128 Xmm6;
SETJMP_FLOAT128 Xmm7;
SETJMP_FLOAT128 Xmm8;
SETJMP_FLOAT128 Xmm9;
SETJMP_FLOAT128 Xmm10;
SETJMP_FLOAT128 Xmm11;
SETJMP_FLOAT128 Xmm12;
SETJMP_FLOAT128 Xmm13;
SETJMP_FLOAT128 Xmm14;
SETJMP_FLOAT128 Xmm15;
} _JUMP_BUFFER;

#elif defined _M_ARM

#define _JBLEN 28
#define _JBTYPE int

typedef struct _JUMP_BUFFER
{
unsigned long Frame;

unsigned long R4;
unsigned long R5;
unsigned long R6;
unsigned long R7;
unsigned long R8;
unsigned long R9;
unsigned long R10;
unsigned long R11;

unsigned long Sp;
unsigned long Pc;
unsigned long Fpscr;
unsigned long long D[8]; // D8-D15 VFP/NEON regs
} _JUMP_BUFFER;

#elif defined _M_ARM64

#define _JBLEN 24
#define _JBTYPE unsigned __int64

typedef struct _JUMP_BUFFER {
unsigned __int64 Frame;
unsigned __int64 Reserved;
unsigned __int64 X19; // x19 -- x28: callee saved registers
unsigned __int64 X20;
unsigned __int64 X21;
unsigned __int64 X22;
unsigned __int64 X23;
unsigned __int64 X24;
unsigned __int64 X25;
unsigned __int64 X26;
unsigned __int64 X27;
unsigned __int64 X28;
unsigned __int64 Fp; // x29 frame pointer
unsigned __int64 Lr; // x30 link register
unsigned __int64 Sp; // x31 stack pointer
unsigned __int32 Fpcr; // fp control register
unsigned __int32 Fpsr; // fp status register

double D[8]; // D8-D15 FP regs
} _JUMP_BUFFER;



#endif



// Define the buffer type for holding the state information
#ifndef _JMP_BUF_DEFINED
#define _JMP_BUF_DEFINED
typedef _JBTYPE jmp_buf[_JBLEN];
#endif



#ifndef _INC_SETJMPEX
#define setjmp _setjmp
#endif



// Function prototypes
int __cdecl setjmp(
_Out_ jmp_buf _Buf
);

#ifdef __cplusplus
__declspec(noreturn) void __cdecl longjmp(
_In_reads_(_JBLEN) jmp_buf _Buf,
_In_ int _Value
) noexcept(false);
#else
__declspec(noreturn) void __cdecl longjmp(
_In_reads_(_JBLEN) jmp_buf _Buf,
_In_ int _Value
);
#endif


_CRT_END_C_HEADER

#pragma warning(pop) // _VCRUNTIME_DISABLED_WARNINGS
0 Kudos
MWind2
New Contributor III
2,372 Views
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,344 Views

The C code, I assume, is correct. The issue (I presume) is the IVF wrapper placed around the call the C functions that handle the unwinding of the deallocate cleanup code in the call stack. This is non-trivial (and similar to the dtor cleanup in C++), lacking this (by calling longjmp directly) would lead to memory leaks should there be allocated variables/arrays in the call stack.

Thanks for the post.

Jim Dempsey

0 Kudos
IanH
Honored Contributor II
2,318 Views

The buffer that Microsoft VC++ setjmp uses to store the details necessary for longjmp to work needs to be at least 256 bytes on Win64 (it is defined as a 16 element array of 16 byte structures).

But I suspect there's a more fundamental problem. The Windows 64 bit calling convention has more requirements on things like stack layout and exception handling data than the myriad of 32 bit conventions, in part to support language exceptions.  longjmp is implemented via the language exception handling mechanism provided by the operating system.  Fortran doesn't have language exceptions, so the Fortran compiler may be taking some shortcuts here.

The Fortran language standard explicitly prohibits conforming programs from calling setjmp and longjmp (F2018 18.10.1p5).

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,312 Views

Ian,

I've tried this with 1024 bytes on x64, it fails this too.

IFORT Version 2020.0.166

 

>>The Fortran language standard explicitly prohibits conforming programs from calling setjmp and longjmp (F2018 18.10.1p5)

Thanks for the reference. Due to this prohibition, the problem will likely not be fixed.

 

This therefore will require additional coding to handle abend. An example could be by setting a global variable to a non-zero error code that is used to abort procedures.

    call FOO(a, b, c, d)

    if(abend_code /=0) return

 

This may be easier than changing the API's of all procedures.

A lot of repetitious work.

 

Jim Dempsey

 

 

0 Kudos
netphilou31
New Contributor II
2,270 Views

Ian,

Thanks for the clarification on the Fortran standard, and as Jim said it is likely to not be fixed quickly (or not). I have already made some changes in places I could thanks to, as suggested by Jim, a global variable, and a simple return when the workflow was easy to exit. For other cases, I guess I can use the RaiseException API method even if Fortan cannot catch it. I will need to ask people in charge of the calling application to handle it. By the end, it's a shame that a simple couple of setjmp/longjmp cannot be used to implement something like a try...except...finally construct in 64-bit whereas it works perfectly in 32-bit. I guess that C/C++ applications don't have this issue, so as Jim stated, it's probably a bad implementation on the Fortran side of the longjmp wrapper that is faulty, and I find quite easy to rely on (or to change) the Fortran standard just to avoid the need to fix it (but maybe there are more complex and obscure reasons to do that).

To come back on the exception handling in Fortran, I remember a post from Dr. Fortran some years ago, about a request to add this feature in the standard, do you have any information/progress about that? I've seen that Lahey Fortran already implements something similar even this may be an extension of the standard.

Anyway, thanks to your comments and advice.

Best regards,

Phil.

0 Kudos
IanH
Honored Contributor II
2,176 Views

Looking at this further, the underlying issue is that the setjmp library call, such that it is, is implemented by a special C/C++ compiler function and associated C/C++ compiler magic.  The special function does not have the same interface as declared in the setjmp.h header (the special function takes two arguments, the second of which is supposed to be the frame pointer).  You cannot describe this special function to the Fortran compiler, therefore there is no way for the Fortran compiler to correctly call the special function.

(The C standard does not define setjmp to be a library function - it is an implementation defined macro. )

The workaround is to call setjmp in a C or C++ wrapper of your Fortran code.  Example below with a Fortran main that calls the C wrapper that then calls back to a Fortran internal procedure.   This is still all non-conforming as per F2018 restriction quoted above.  

 

 

#include <setjmp.h>

void c_wrapper(void(*proc)(jmp_buf env))
{
   jmp_buf env;
   int value = setjmp(env);
   if (value == 0) proc(env);
}
PROGRAM longjmp_bail
  USE, INTRINSIC :: ISO_C_BINDING, ONLY: C_INT
  IMPLICIT NONE
  
  INTERFACE
    SUBROUTINE c_wrapper(proc) BIND(C)
      IMPORT :: C_INT
      IMPLICIT NONE
      INTERFACE
        SUBROUTINE proc(env) BIND(C)
          IMPORT :: C_INT
          IMPLICIT NONE
          INTEGER(C_INT) :: env(*)
        END SUBROUTINE proc
      END INTERFACE
    END SUBROUTINE c_wrapper
    
    SUBROUTINE longjmp(env, value) BIND(C)
      IMPORT :: C_INT
      IMPLICIT NONE
      INTEGER(C_INT) :: env(*)
      INTEGER(C_INT), VALUE :: value
    END SUBROUTINE longjmp
  END INTERFACE
  
  CALL c_wrapper(proc)
  PRINT "('Done')"
CONTAINS
  SUBROUTINE proc(env) BIND(C)
    INTEGER(C_INT) :: env(*)
    PRINT "('Proc start')"
    CALL longjmp(env, 1)
    PRINT "('Proc finish')"
  END SUBROUTINE proc
END PROGRAM longjmp_bail
​
0 Kudos
netphilou31
New Contributor II
2,119 Views

Ian,

Thanks for your code sample. I keep it in mind in case I need to use it, but as you mentioned, since it is not conforming to Fortran standards, I will probably replace the setjmp/longjmp calls by a simple RaiseException API call. I already use it in another dll, but here I need to discuss with developers in charge of the GUI to catch this exception and apply the appropriate actions.

Phil.

0 Kudos
jimdempseyatthecove
Honored Contributor III
2,256 Views

>>whereas it works perfectly in 32-bit.

This may work by accident rather than design. Performing the abend return is but one aspect of being perfectly, cleaning the (potential) heap allocations is a second requirement. To verify this to yourself, after the setjmp, call a subroutine and perform an allocation to a local array, then in that subroutine perform the longjmp simulating an error condition. You will (should) find that the allocation was not returned. Additionally, a similar issue will be involved with file units that are controlled within your DLL (units that were designed to be closed prior to return from DLL won't get closed and thus won't be available on next call to the DLL).

 

While this may not be an issue (or at least not observed with testing at your development machine), may present itself as a major issue in a production environment.

 

Jim Dempsey

0 Kudos
Reply