Native Code Integration

Native Code Integration

Integrating Native Code with X10 Programs

Justification

There are several reasons for wanting to integrate some native code with an X10 program:

  • Incremental porting of an application to X10
  • Making use of an existing native library (e.g. blas)
  • Writing ASM or other tightly-controlled code

Approach

The primary mechanism X10 programmers should use is annotations. The annotations override the usual code generation strategy used by the X10 compiler, allowing the programmer to insert verbatim C++ or Java code into their X10 program.  In the following example, the string on the static method being called is used verbatim at the call site.  Because calls are expressions, the native annotation ought to be an expression too.

Test.x10
import x10.compiler.Native;

public class Test {

    // Use native code in all backends:
    @Native("c++","printf(\"Hello World!\\n\")")
    @Native("java","System.out.println(\"Hello World!\")")
    private static native def test1 () : void;

    // Only use native code in C++ backend:
    @Native("c++","printf(\"Hello World!\\n\")")
    private static def test2 ():void {
        // X10 code provides behaviour for Java backend.
    }

    // Use function parameters in native code
    // #0 is the name of the class (Test in this case)
    // #1, #2, #3, etc name the parameters
    @Native("c++","printf(\"This is the number %d\\n\", (#1))")
    @Native("java","System.out.println(\"This is the number \"+(#1))")
    private static native def test3 (x:Int) : void;

    public static def main (args:Rail[String]) {
        test1();
        test2();
        test3(42);
    }
}

One can also annotate a block directly: (in this case you are providing a native statement instead of an expression, so do not forget the semicolon)

Test.x10
import x10.compiler.Native;

public class Test {

    public static def main (args:Rail[String]) {
        val x = 42;
        // Can access parameters directly
        { @Native("c++","printf(\"Hello World! %d\\n\", x);") {} }
    }
}

External C++ code

Sometimes C++ code needs to be linked from manually written C++ files. There are two ways to do this.

Auxiliary C++ files

Writing code in @Native annotations can be tiresome, so sometimes it is best to write functions in .cc files that sit next to the X10 file that uses them. To add these files into the compilation, there are additional annotations:

MyCppCode.h
void foo();
MyCppCode.cc
#include <cstdlib>
#include <cstdio>
void foo() {
    printf("Hello World!\n");
}
Test.x10
import x10.compiler.Native;
import x10.compiler.NativeCPPInclude;
import x10.compiler.NativeCPPCompilationUnit;

@NativeCPPInclude("MyCppCode.h")
@NativeCPPCompilationUnit("MyCppCode.cc")
public class Test {
    public static def main (args:Rail[String]) {
        { @Native("c++","foo();") {} }
    }
}

The additional files should be in the same directory as the X10 file and will be copied to the output directory if specified (x10c++ -d out_dir).

System libraries

If we want to additionally link to more libraries in /usr/lib for example, it is necessary to adjust the post compile directly. The mechanism used for this is the -post commandline parameter to x10c+. The following example shows how to compile blas into the executable via post compiler (e.g. g++) parameters.

 

x10c++ Test.x10 -post '# # -I /usr/local/blas # -L /usr/local/blas -lblas'

  • The first # means to use the default compiler for the architecture (from x10rt properties file).
  • The second # is substituted for the .cc files and CXXFLAGS that would ordinarily be used.
  • The third # is substituted for the libraries and LDFLAGS that would ordinarily used
  • For the second and third, if a % is used instead of a # then the parameters are suppressed and suitable parameters must be provided manually. This allows a complete override of the postcompiler behaviour.