Skip to content

dataPulverizer/interface-d-c-fortran

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

64 Commits
 
 
 
 

Repository files navigation

Interfacing D with C and Fortran

Introduction

The C and Fortran programming languages have many popular libraries that are heavily relied upon. D's generic and meta-programming features, as well as its multi-paradigm nature and clear syntax makes it a great choice for various applications. Many programming languages allow varying degrees of compatibility with C and Fortran libraries. However C libraries have full compatibility with D, and Fortran subroutines can be called from D. This article describes how to interface D code with C and Fortran. This includes outlining the ease in which functions in C standard libraries can be accessed from D, calling functions from C static libraries, calling D functions from C, and calling Fortran subroutines from D. The article also covers how to use D's string mixins to generate code to avoid repetitive writing of wrapper functions and declarations.

The D code in this article uses -betterC style. This mode of programming is increasingly popular in D circles focusing on D as a replacement for C in systems programming. The -betterC flag in the D compiler uses a ligth-weight subset of D and has the side-effect of facilitating an integration of D code to C that is as seamless as calling C code from D.

The examples presented here are run on a Linux Ubuntu 64-bit 16.04 system and use the gcc 5.4.0 C and Fortran compilers and ldc2 version 1.1.0 D's LLVM-based D compiler.

Calling C functions from D.

The math.h header describes a standard library in C. Example1 code below shows the D code for importing the fabs and pow functions from this library. The extern (C) braces contain functions to be imported from C. The qualifiers nogc and nothrow are used because the imported C functions are not garbage collected and do not throw exceptions.

In general the C language all too easily allows unsafe actions one of which is corrupting data. Some of the imported functions are modified with @safe which ensures that they do not carry out a number of potentially unsafe actions. The printf function is not marked with @safe but instead the scope statement is used because the pointer is not preserved internally.

/* example1.d */

@nogc nothrow: 

/* Declare C functions */
extern(C)
{
    double pow(double x, double y) @safe;
    double fabs(double x) pure @safe;
    int printf(scope const char* format, ...);
}

pragma(inline, false)
void printArray(double[] arr)
{
    foreach(elem; arr)
        printf("%f ", elem);
    printf("\n");
}

pragma(inline, true)
void apply(alias fun)(double[] arr)
{
    foreach(ref elem; arr)
        elem = fun(elem);
}

__gshared double[] x = [-1, 2, -3, 4, -5 , 6];

extern(C)
int main()
{
    apply!fabs(x);
    printArray(x);
    apply!(a => pow(a, 2.5))(x);
    printArray(x);
    return 0;
}
$ ldc2 -betterC -Oz -release -linkonce-templates -run example1.d
1.000000 2.000000 3.000000 4.000000 5.000000 6.000000 
1.000000 5.656854 15.588457 32.000000 55.901699 88.181631 

The following are helpful descriptors of the code above:

  • Full descriptions of the flags for the ldc2 compiler are given with the -help flag, but the current flags are to reduce code size and bloat and create light-weight executables.

  • If you are new to D the apply function is an example of using meta-programming techniques to pass a function using the template alias parameters. More details on template meta-programming can be found on Philippe Sigaud's tutorial. In addition, the Mir and Phobos libraries offer map functions that iterate over arrays.

  • Purity of functions can be enforced in D using the pure qualifier to indicate that the function can not have side-effects which is the case with the fabs function.

  • The printArray function is used multiple times and can be explicitly marked with pragma(inline, false).

  • The __gshared qualifier indicates global shared data - the default behaviour in C/C++, it places x in the DATA section of the object file.

  • Garbage collection and execptions are not used so extern(C) declares common C _main instead of D __Dmain. This allows the final executable to be reduced in size by 72 times (from 617.5 KB to 8.5 KB).

The nm tool allows the object file symbols to be inspected. The output shows the economy of the created symbols, it omits the creation of code that increases file size and perhaps efficiency of the executable.

$ ldc2 -betterC -nogc -Oz -release -linkonce-templates -c example1.d
$ nm example1.o
0000000000000000 T __D7article10printArrayFNbNiAdZv
0000000000000050 T _main
0000000000000170 D __D7article1xAd
0000000000000180 d _.constarray
                 U _fabs
                 U _pow
                 U _printf
                 U _putchar

The symbols in the object file that can be described as:

  • __D7article10printArrayFNbNiAdZv - printArray, D mangling.
  • _main - main, C mangling.
  • __D7article1xAd - x's length and pointer
  • _.constarray - x's data
  • _fabs, _pow, _printf - extern C symbols.
  • _putchar - extern C symbol, optimized version for printf("\n")

Calling static C libraries from D

The main difference between calling static C libraries from D and calling installed C libraries is in the compilation process. Below is an example of a simple multiplication function written in C:

/* example2c.c */
double mult(double x, double y)
{
    return x * y;
}

The D code to call this similar to our previous example:

/* example2d.d */
extern(C) @nogc nothrow:

double mult(double x, double y) @safe pure;
int printf(scope const char* format, ...);

extern(C)
int main()
{
    printf("%f\n", mult(3, 4));
    return 0;
}

There are many ways to compile these two scripts. Below the C and D scripts are compiled into separate object files. Then both object files are compiled together with the gcc compiler. Another option is to compile the C code to an object file and then use the D compiler to compile the C object file together with the .d script(s) to generate the executable.

$ gcc -O -c example2c.c
$ ldc2 -Oz -c -release -betterC -nogc example2d.d
$ gcc example2c.o example2d.o -oexample2
$ ./example2
12.000000

# in addition
$ nm example2c.o
0000000000000000 T _mult
$ nm example2d.o
0000000000000000 T _main
                 U _mult
                 U _printf

The code above is given here.

Calling D functions from C

Calling D from C is a less seamless affair because D has features that are not supported in C, however as before, -betterC can be used to create compatitable object files. The D code below creates multiplication functions of different types using string mixins. String mixins are a meta-programming technique that allows the user to generate code with strings at compile time.

/* example3d.d */
extern (C) nothrow @nogc @safe pure:

/* Template to generate string */
template GenWrap(string type)
{
    enum string GenWrap = type ~ " " ~ type[0] ~ "mult(" ~ type ~ " x, " ~ type ~ " y)\n{\n\treturn x*y;\n}";
}

mixin(GenWrap!"double");
mixin(GenWrap!"float");

So mixin(GenWrap!"double") will expand to the code:

double dmult(double x, double y){
    return x*y;
}

Instead of using quotes some users prefer using D's q{} notation to represent strings, in this case, syntactic D code is held in the curly braces. We will be revising the use of mixins in the final example to generate Fortran code.

C code then references and calls the functions created in D.

/* example3c.c */
#include <stdio.h>

extern double dmult(double x, double y);
extern float fmult(float x, float y);;

int main()
{
    double xd = 3.0, yd = 4.0;
    float xf = 3.0, yf = 4.0;
    printf("output: %f\n", dmult(xd, yd));
    printf("output: %f\n", fmult(xf, yf));
    return 0;
}

To compile:

$ gcc -O -c example3c.c
$ ldc2 -O -release -betterC -linkonce-templates -c example3d.d
$ gcc example3c.o example3d.o -oexample3
$ ./example3
output: 12.000000
output: 12.000000

# in addition
$ nm example3c.o
                 U _dmult
                 U _fmult
0000000000000000 T _main
                 U _printf
$ nm example3d.o
0000000000000000 T _dmult
0000000000000010 T _fmult

The above code is located here.

Calling Fortran code from D

There are many numeric libraries written in Fortran that are still frequently used. It is thus important that they can be accessed from D. Fortran subroutines can be called directly from D in a very similar way to calling C from D. If you are working with Fortran and D, it is a good idea to explore Fortran Iso C Binding documentation. Below is a Fortran subroutine equivalent of the multiplication function:

!example4f.f90
SUBROUTINE MULT(x, y)
IMPLICIT NONE
REAL*8, INTENT(IN) :: x
REAL*8, INTENT(INOUT) :: y
y = x*y
END SUBROUTINE MULT

The main difference between calling C code and Fortran code from D is that the inputs to the Fortran subroutines must be referenced and the name of the function is mangled in the Fortran object file to include an underscore afterwards. C-style notation can be used double mult_(double* x, double* y);, however D provides ref notation which allows referenced inputs without requring pointers:

/* example4d.d */
extern (C) nothrow @nogc:

double mult_(ref double x, ref double y) @safe pure;
int printf(scope const char* format, ...);

int main(){
    double x = 4, y = 5;
    printf("%f\n", mult_(x, y));
    return 0;
}

Compilation is similar to calling C from D:

gfortran -c example4f.f90
ldc2 -ofexample4 example4d.d example4f.o && ./example4

The output from the Fortran object file showing the underscore mangled append name:

$ nm example4f.o 0000000000000000 T mult_

The code for the above is located here.

This link is a useful resource for Fortran and this is useful for compilation hints.

Calling Fortran code from D with mixins

Consider a situation where Fortran subroutines of the same signature but with different names need to be called in D. D can be used to generate the necessary declarative and wrapper code to avoid repetitive code. As an example consider porting the the following trigonometric subroutines from Fortran:

!example5f.f90
SUBROUTINE SIN(x)
IMPLICIT NONE
REAL*8, INTENT(INOUT) :: x
x = DSIN(x)
END SUBROUTINE SIN

SUBROUTINE COS(x)
IMPLICIT NONE
REAL*8, INTENT(INOUT) :: x
x = DCOS(x)
END SUBROUTINE COS

SUBROUTINE TAN(x)
IMPLICIT NONE
REAL*8, INTENT(INOUT) :: x
x = DTAN(x)
END SUBROUTINE TAN

SUBROUTINE ATAN(x)
IMPLICIT NONE
REAL*8, INTENT(INOUT) :: x
x = DATAN(x)
END SUBROUTINE ATAN

We can use templates to generate strings at compile-time. These strings are compile-time "interpreted" into code using string mixins and templates.

The first template is used to generate the declarations under extern (C):

template Declare(string fun)
{
    enum string Declare = "double " ~ fun ~ "_(ref double x) pure;";
}

Declare!"sin" will generate the string double sin_(ref double x) pure; which declares the ported Fortran sin_ function. Next is the wrapper function to allow the user to use sin(x) rather than sin_(x):

template Wrap(string fun)
{
    enum string Wrap = "double " ~ fun ~ "(double x)\n{\n    return " ~ fun ~ "_(x);\n}";
}

Then a template function that recursively concatenates the outputs for many functions to generate one string for all the functions is given below.

template GenFuns(string[] funs, alias wrapper)
{
    static if(funs.length > 0)
        enum string GenFuns = wrapper!(funs[0]) ~ GenFuns!(funs[1..$], wrapper);
    else
        enum string GenFuns = "";
}

The complete D script for this is given below:

/* example5d.d */
template Declare(string fun)
{
    enum string Declare = "double " ~ fun ~ "_(ref double x) pure;";
}

template Wrap(string fun)
{
    enum string Wrap = "double " ~ fun ~ "(double x)\n{\n    return " ~ fun ~ "_(x);\n}";
}

template GenFuns(string[] funs, alias wrapper)
{
    static if(funs.length > 0)
        enum string GenFuns = wrapper!(funs[0]) ~ GenFuns!(funs[1..$], wrapper);
    else
        enum string GenFuns = "";
}

/* Name of the functions to be ported */
immutable(string)[4] trigFuns = ["sin", "cos", "tan", "atan"];

extern(C) nothrow @nogc
{
    int printf(scope const char* format, ...);
    mixin(GenFuns!(trigFuns, Declare));
}

mixin(GenFuns!(trigFuns, Wrap));

extern (C):
int main(){
    double pii = 1;
    immutable double pi = 4*atan(pii);
    assert(sin(pi/2) == 1);
    assert(cos(0) == 1);
    assert(tan(0) == 0);
    printf("Yay!\n");
    return 0;
}

The make code is below:

example5 : example5d.d example5f.o
    ldc2 -O -release -betterC -linkonce-templates -oftrig example5d.d example5f.o
example5f.o : example5f.f90
    gfortran -O -c example5f.f90
.PHONY : clean
clean :
    rm example5 *.o

After running make, ./example5 will return the output "Yay!"

The code for this section is given here.

Summary

This article shows that D can interface with C and Fortran simply and efficiently. In the case of calling C functions from D, there is minimal effort required in that the function(s) need only be declared under extern (C). In the case of calling D code from C, efforts need to be made to remove features in D that are incompatible with C using -betterC and other appropriate flags.

Calling Fortran libraries from D is almost as straightforward as calling C from D, however Fortran mangles the exported names with a postfix underscore and arguments on the D code side must be referenced either using ref or with C-style pointers. D's mixins can be used to generate repetitive sections of code that can make it easier to port functions that have the same general call structure.

About

Interfacing D with C and Fortran

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published