I am trying to compile some very old Fortran procedures in a DLL, so that to be able to use them with Delphi. Although the Fortran code is not very large (750-800 lines), its structure is very complicated with dozens of GOTO commands and the translation is not easy (I tried to make some useful code of it, but I failed).
Although I am new in Fortran, and not very experienced in calling DLLs, I gradually managed to overcome all the difficulties but for one, that is to be able to call the Fortran Subroutine with multiple dynamic arrays. Here’s a simple example that I created:
SUBROUTINE MYSUB1( NoEquations, INTARR1 )
!DEC$ ATTRIBUTES DLLEXPORT::MYSUB1
!DEC$ ATTRIBUTES C, REFERENCE, ALIAS:'MYSUB1' :: MYSUB1
C
C***************************************************************
C
INTEGER NoEquations, I
INTEGER INTARR1(*)
C
C***************************************************************
C
DO 100, I=1,NoEquations
INTARR1(I) = I
100 CONTINUE
RETURN
C
END
SUBROUTINE MYSUB2( NoEquations, INTARR1, INTARR2 )
!DEC$ ATTRIBUTES DLLEXPORT::MYSUB2
!DEC$ ATTRIBUTES C, REFERENCE, ALIAS:'MYSUB2' :: MYSUB2
C
C***************************************************************
C
INTEGER NoEquations, I
INTEGER INTARR1(*)
INTEGER INTARR2(*)
C
C***************************************************************
C
DO 100, I=1,NoEquations
INTARR2(I) = INTARR1(I)
100 CONTINUE
RETURN
C
END
I compile the Fortran code with mingw-w64 with the following command:
gfortran -shared -mrtd -fno-underscoring -o simple.dll simple.f
And I declare the procedure from within Delphi with:
procedure mysub1(var NoEquations: integer; var INTARR1 : array of integer); stdcall; external 'simple.dll';
procedure mysub2(var NoEquations: integer; var INTARR1,INTARR2: array of integer); stdcall; external 'simple.dll';
The Delphi proram compiles correctly, but when I run it, mysub1 works correctly and updates INTARR1, but mysub2 gives me an Access Violation. Obviously, the second dynamic array confuses the compiler, but I do not know how to make it understand.
Thanks in advance
I do not know Delphi, but here is what you can do to create a DLL accessible from the C language. I hope you find it helpful. Here is your F77 code modified to make it interoperable via iso_c_binding module features of Fortran:
SUBROUTINE MYSUB1(NoEquations,INTARR1) bind(C,name="MYSUB1")
!DEC$ ATTRIBUTES DLLEXPORT :: MYSUB1
use, intrinsic :: iso_c_binding, only: IK => c_int32_t
integer(IK), intent(in), value :: NoEquations
integer(IK), intent(out) :: INTARR1(NoEquations)
integer :: I
DO 100, I=1,NoEquations
INTARR1(I) = I
100 CONTINUE
RETURN
END SUBROUTINE MYSUB1
C***************************************************************
C***************************************************************
SUBROUTINE MYSUB2(NoEquations,INTARR1,INTARR2)
+bind(C,name="MYSUB2")
!DEC$ ATTRIBUTES DLLEXPORT :: MYSUB2
use, intrinsic :: iso_c_binding, only: IK => c_int32_t
integer(IK), intent(in), value :: NoEquations
integer(IK), intent(in) :: INTARR1(NoEquations)
integer(IK), intent(out) :: INTARR2(NoEquations)
integer :: I
DO 100, I=1,NoEquations
INTARR2(I) = INTARR1(I)
100 CONTINUE
RETURN
END SUBROUTINE MYSUB2
Note the many subtle but important changes that I have made to your code to make it interoperable with C:
The bind(C,name="MYSUB1") fixes the subroutine's name so you do not need the extra second compiler directives (which I have now removed from your code).
Also, note the value attribute which tells the compiler to pass by value as is done in C.
Also, note that I define the kinds of integers in the interfaces of the subroutine as c_int32_t to be compatible with the C processor's integer kinds.
Also, note that I have converted your assumed-size INTARR1 and INTARR2 arrays to explicit-shape arrays INTARR1(NoEquations), INTARR1(NoEquations).
Since you have the Intel !DEC$ compiler directives in your code, I assume that you are using the Intel Fortran compiler on Windows. Note that I removed your second lines of directives in both Fortran subroutines.
Now, assuming that you store the above code in a file named mysubs.F, then compiling this file via the Intel Fortran compiler ifort on the Intel-Windows command prompt as in the following command,
ifort mysubs.F /dll /out:libsubs
will generate a DLL named mysubs.dll in the current folder and print the following message on the screen,
Intel(R) Visual Fortran Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.1.216 Build 20200306
Copyright (C) 1985-2020 Intel Corporation. All rights reserved.
Microsoft (R) Incremental Linker Version 14.16.27027.1
Copyright (C) Microsoft Corporation. All rights reserved.
-out:mysubs.dll
-dll
-implib:mysubs.lib
mysubs.obj
Creating library mysubs.lib and object mysubs.exp
To test this DLL, you can try the following C code stored in main.c,
#include <stdio.h>
#include <stdint.h>
#include <string.h>
void MYSUB1(int32_t, int32_t []);
void MYSUB2(int32_t, int32_t [], int32_t []);
int main(int argc, char *argv[])
{
const int32_t NoEquations = 5;
int32_t INTARR1[NoEquations];
int32_t INTARR2[NoEquations];
int loop;
// C rules for argument passing apply here
MYSUB1(NoEquations,INTARR1);
printf("\nINTARR1:\n"); for(loop = 0; loop < NoEquations; loop++) printf("%d ", INTARR1[loop]);
MYSUB2(NoEquations,INTARR1,INTARR2);
printf("\nINTARR2:\n"); for(loop = 0; loop < NoEquations; loop++) printf("%d ", INTARR2[loop]);
return 0;
}
Compiling this C code with the Intel C compiler,
icl main.c -c
prints the following on screen,
Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.1.216 Build 20200306
Copyright (C) 1985-2020 Intel Corporation. All rights reserved.
main.c
and generates the C main object file. Finally, link the C object file with the Fortran DLL library to generate the executable via the following command,
icl main.obj mysubs.lib -o main.exe
which prints the following on screen,
Intel(R) C++ Intel(R) 64 Compiler for applications running on Intel(R) 64, Version 19.1.1.216 Build 20200306
Copyright (C) 1985-2020 Intel Corporation. All rights reserved.
Microsoft (R) Incremental Linker Version 14.16.27027.1
Copyright (C) Microsoft Corporation. All rights reserved.
-out:main.exe
main.obj
mysubs.lib
Testing the DLL call. Simply call the generated executable main.exe,
main.exe
which prints on screen,
INTARR1:
1 2 3 4 5
INTARR2:
1 2 3 4 5
Now, to call this DLL from Delphi, simply assume that you are calling the C functions with the prototypes specified in the C main code. That's all. No further dealing with Fortran from inside Delphi.
Final advice:
Fortran has powerful standard interoperability features, like the ones I have added to your F77 code, that can easily connect almost any Fortran code to any language (via C).
Stay away from FORTRAN77 which is almost a half-century old, even Fortran 90 is already more than 3 decades old. The latest Fortran standard was released in 2018, which together with Fortran 2008, makes Fortran an extremely powerful, high-level, fast, natively vectorized, concurrent, shared, and distributed parallel programming language for numerical computing.
Related
I would like to read if the value of the SError mask bit is set. Previously in case ARMv7 it was easy accessible by CSPR register, however in case of ARMv8-A and aarch64 that is no longer so - from ARMv8-A Programmers Guide:
"AArch64 does not have a direct equivalent of the ARMv7 Current Program Status Register
(CPSR). In AArch64, the components of the traditional CPSR are supplied as fields that can be
made accessible independently. These are referred to collectively as Processor State (PSTATE)."
so since PSTATE is not an actual register then:
mrs x0, pstate
gives me an error on compilation. So how can I read this PSTATE to get the value of SError that I need?
AArch64 has separate registers for various subsets of PSTATE. For the interrupt masks, this is called daif:
mrs x0, daif
The D, A, I and F flags are in bits 9, 8, 7 and 6 respectively (mask 0x3c0). The other bits are reserved and should read as zero.
I am trying to learn more about compilers and RISC V assembly was specifically designed to be easy to learn and teach. I am interested in compiling some simple C code to assembly using clang for the purpose of understanding the semantics. I'm planning on using venus to step through the assembly and the source code does NOT actually need to be fully compiled to machine code in order to run on a real machine.
I want to avoid compiler optimizations so I can see what I've actually instructed the processor to do.
I don't actually need the program to compile to machine code--I just want the assembly.
I don't want to worry about linking to the system library because this code doesn't actually need to run
The code does not make any explicit use of system calls and so I think a std lib should not be required
This answer seems to indicate that clang definitely can compile to RISC V targets, but it requires having a version of the OS's standard library built for RISC V.
This answer indicates that some form of cross-compiling is necessary, but again I don't need to fully compile the code to machine instructions so this should not apply if I'm understanding correctly.
Use clang -S to stop after generating an assembly file:
$ cat foo.c
int main() { return 2+2; }
$ clang -target riscv64 -S foo.c
$ cat foo.s
.text
.attribute 4, 16
.attribute 5, "rv64i2p0_m2p0_a2p0_c2p0"
.file "foo.c"
.globl main
.p2align 1
.type main,#function
main:
addi sp, sp, -32
sd ra, 24(sp)
sd s0, 16(sp)
addi s0, sp, 32
li a0, 0
sw a0, -20(s0)
li a0, 4
ld ra, 24(sp)
ld s0, 16(sp)
addi sp, sp, 32
ret
.Lfunc_end0:
.size main, .Lfunc_end0-main
.ident "Ubuntu clang version 14.0.0-1ubuntu1"
.section ".note.GNU-stack","",#progbits
.addrsig
You can also use Compiler Explorer conveniently online.
I am using GlobalMemoryStatusEX in order to find out the amount of memory in my system.
Is there a similar way to find the amount of memory on my graphics card?
Here is a piece of my code :
use kernel32
use ifwinty
implicit none
type(T_MEMORYSTATUSEX) :: status
integer(8) :: RetVal
status%dwLength = sizeof(status)
RetVal = GlobalMemoryStatusEX(status)
write(*,*) 'Memory Available =',status%ullAvailPhys
I am using Intel Visual Fortran 2010 on Windows 7 x64.
Thank you!
Since you tagged this question with the CUDA tag, I'll offer a CUDA answer. Not sure if it really makes sense given your environment.
I haven't tested this on IVF, but it works on gfortran and PGI fortran (linux). You can use the fortran iso_c_binding module available in many implementations to directly call routines from the CUDA runtime API library in fortran code. One of those routines is cudaMemGetInfo.
Here's a fully worked example of calling it from gfortran (on linux):
$ cat cuda_mem.f90
!=======================================================================================================================
!Interface to cuda C subroutines
!=======================================================================================================================
module cuda_rt
use iso_c_binding
interface
!
integer (c_int) function cudaMemGetInfo(fre, tot) bind(C, name="cudaMemGetInfo")
use iso_c_binding
implicit none
type(c_ptr),value :: fre
type(c_ptr),value :: tot
end function cudaMemGetInfo
!
end interface
end module cuda_rt
!=======================================================================================================================
program main
!=======================================================================================================================
use iso_c_binding
use cuda_rt
type(c_ptr) :: cpfre, cptot
integer*8, target :: freemem, totmem
integer*4 :: stat
freemem = 0
totmem = 0
cpfre = c_loc(freemem)
cptot = c_loc(totmem)
stat = cudaMemGetInfo(cpfre, cptot)
if (stat .ne. 0 ) then
write (*,*)
write (*, '(A, I2)') " CUDA error: ", stat
write (*,*)
stop
end if
write (*, '(A, I10)') " free: ", freemem
write (*, '(A, I10)') " total: ", totmem
write (*,*)
end program main
$ gfortran -O3 cuda_mem.f90 -L/usr/local/cuda/lib64 -lcudart -o cuda_mem
$ ./cuda_mem
free: 2755256320
total: 2817982464
$
In windows, you would need to have a properly installed CUDA environment, (which presumes visual studio). You would then need to locate the cudart.lib in that install, and link against that. I'm not 100% sure this would link successfully in IVF, since I don't know if it would link similarly to the way VS libraries link.
I just use gfortran 4.1.2 and gfortran 4.8.0 to compile the following simple code:
function foo(a, b) result(res)
integer, intent(in) :: a, b
integer res
res = a+b
end function foo
program test
integer a, b, c
c = foo(a, b)
end program test
gfortran 4.1.2 succeeds, but gfortran 4.8.0 gives the weird error:
test.F90:14.11:
c = foo(a, b)
1
Error: Return type mismatch of function 'foo' at (1) (REAL(4)/INTEGER(4))
Any idea?
There is a bug in your code, namely that you don't specify the return type of the function foo in the main program. Per the Fortran implicit typing rules it thus gets a type of default real.
You should (1) always use 'implicit none', furthermore if at all possible, (2) use modules or contained procedures thus giving you explicit interfaces.
The reason why GFortran 4.1 doesn't report this error is that older versions of GFortran always functioned in a 'procedure at a time' mode; thus the compiler is happily oblivious to any other functions in the same file. Newer versions work in 'whole file' mode (default since 4.6) where the compiler 'sees' all the procedures in a file at a time. This allows the compiler to catch errors such as the one in your code, and also provides some optimization opportunities.
I am new to this and here is what I want to do -
Create simple programs - loops, counters etc. using ARMv7 assembly
I want to be able to compile them on the Mac / Win / Linux for running on the iPhone
I have a jailbroken iPhone so I can upload the file there, sign it with ldid and run it
Can someone please point me to how I can do this with freely available tools?
Thanks!
I'm left wondering what you're trying to achieve here - is it to learn developing in ARM assembler? Do you want to write iOS applications?
No sane person writes complete applications in assembler these days - they use high level languages - and in a few restricted cases optimize in assembler. This is a very specialist and useful skill to have.
Using a complete C program as a surrogate host is good way to start. Create yourself a simple Hello world program in C which calls an (almost) empty function.
You can (mostly) get this to work using XCode (you need install the optional command-line tools). All but the final linking stage for ARM works using clang. This is obviously MacOSX only.
A better alternative for this kind of experimentation is an ARM Linux system where you're not fighting against the locked down environment of iOS. The Raspberry Pi is perfect for the job. You'll need a cross-compiling toolchain for ARMv7 - of which there are plenty. If using Ubuntu, there are pre-built packages readily available.
Main.c
#include<stdio.h>
extern void func();
int main()
{
printf("Hello World\n");
func();
}
and func.c
#include <stdio.h>
void func()
{
printf("In func()\n");
}
Compile both for your host environment and run it to see it works:
gcc main.c func.c
`./a.out'
Now compile for your target environment. The precise name of the cross-compiling tools varies depending what you installed (mine is arm-angstrom-linux-gnueabi-gcc)
arm-angstrom-linux-gnueabi-gcc main.c func.c
Copy to your target, and prove it works.
Now you can start to write some assembler. Get gcc to produce ARM assembler for our victim file func.c - this results in a file func.s
arm-angstrom-linux-gnueabi-gcc func.c -s
:
.cpu arm7tdmi-s
.fpu softvfp
.eabi_attribute 20, 1
.eabi_attribute 21, 1
.eabi_attribute 23, 3
.eabi_attribute 24, 1
.eabi_attribute 25, 1
.eabi_attribute 26, 2
.eabi_attribute 30, 6
.eabi_attribute 18, 4
.file "func.c"
.section .rodata
.align 2
.LC0:
.ascii "In func()\000"
.text
.align 2
.global func
.type func, %function
func:
# Function supports interworking.
# args = 0, pretend = 0, frame = 0
# frame_needed = 1, uses_anonymous_args = 0
stmfd sp!, {fp, lr}
add fp, sp, #4
ldr r0, .L2
bl puts
sub sp, fp, #4
ldmfd sp!, {fp, lr}
bx lr
.L3:
.align 2
.L2:
.word .LC0
.size func, .-func
.ident "GCC: (GNU) 4.5.4 20120305 (prerelease)"
.section .note.GNU-stack,"",%progbits
You can see here that between label func: and .L3 is the business end of func() - and it's almost all function prologue and epilogue. You'll want to check out the ARM Procedure Call Standard to understand what these are and for guidance on which registers to use.
Once you've done your edits, compile the whole thing again with GCC
arm-angstrom-linux-gnueabi-gcc main.c func.s
...and test it.