This page documents the methods available to connect Swift/T to leaf tasks that may be C, C++, or Fortran functions (native code).

Links:

To report problems with these examples, post to the ExM user list.

1. Overview

Two methods are available for connecting a leaf function: simple mode and advanced mode. Both call to Tcl. It is easy to call from Tcl to native code, as presented in this guide. We tap into SWIG which allows us to use a standard language-language tool to help automate this process. (SWIG is a nice skill to have in general, so the techniques here are usable outside Swift as well!)

The modes have separate syntax:

Simple:

(int o) f(int i) "my_pkg" "version"
[ "set <<o>> [ f <<i>> ]" ];

Thus, in the simple mode the user specifies the exact Tcl line to execute. Data dependencies are handled by Swift. Thus, that Tcl line runs after i has a value and o is a writable variable.

More complex Tcl programming can be done on that line. However, it is probably best to pack any additional logic into the Tcl function f.

Advanced:

(int o) f(int i) "my_pkg" "version" "f";

In the advanced mode, f is issued before i has a value. The user must use the Turbine API to set up execution dependent on i. This guide will demonstrate how to do that.

Additionally, you may call Swift/T from native code.

2. Examples

Complete code samples for each example are found in this tar file:

When you unpack this file, you will have a directory examples/. Change to this directory and source setup.sh to obtain build settings.

You can use the clean.sh scripts to clean up.

Note
We often use shell options set -eux.
Note
You may need to modify some scripts to set the location of Tcl or other system tools.

3. Simple mode

Calling Tcl from Swift in simple mode is easy. However, the user does have to have a basic understanding of Tcl. In typical cases, the user will want to make a Tcl package so that multiple complex Tcl functions can be called, which may link to native code.

3.1. Complete example 1: Pure Tcl function

Consider the Tcl source file f.tcl:

package provide my_pkg 0.1

namespace eval my_pkg {
    proc f { x y } {
        return [ expr $x + $y ]
    }
}

This simply defines a function f that adds two numbers together. Tcl command puts prints the result (as puts() in C). Thus, writing a Tcl function for Swift involves:

  1. creating a namespace (which will be used to group functions into a package) and

  2. defining Tcl functions to call.

It may be tested with this test (test-f-1.tcl):

source "f.tcl"
set z [ my_pkg::f 2 3 ]
puts $z

Calling this from the shell looks like this:

$ tclsh test-f-1.tcl
5
Note
On your system, the Tcl shell tclsh may be called tclsh8.5

Thus, to load this into Swift, we need to be able to:

  1. load f.tcl and

  2. invoke it.

To make it easy to load f.tcl, we create a Tcl package for it. This is done with a simple Tcl line that groups together a package name. Conventionally this is put in make-package.tcl:

puts [ ::pkg::create -name my_pkg -version 0.1 -source f.tcl ]

This simply includes the name, version, and Tcl source files to put in the package. Multiple -source arguments may be used.

The Tcl package is represented in a file that must be called pkgIndex.tcl. This directory containing this file is found by Tcl via the TCLLIBPATH variable. (This is similar to LD_LIBRARY_PATH, Java’s CLASSPATH, Python’s PYTHONPATH, etc.).

Note
TCLLIBPATH is space-separated (which makes for easier Tcl processing).
Note
TCLLIBPATH is modified by Turbine. To expose a Tcl package to Swift/T, set SWIFT_PATH.

We create pkgIndex.tcl from the shell:

$ tclsh make-package.tcl > pkgIndex.tcl

Now, we can test our function f by accessing through the package (instead of a simple source command). First the Tcl to load and run the package (test-f-2.tcl):

package require my_pkg 0.1
set z [ my_pkg::f 2 3 ]
puts $z

Now, invoke this test from the shell (test-f-2.sh):

$ export TCLLIBPATH=$PWD
$ tclsh test-f-2.tcl
5

The next step is to call this from Swift. First, define the typed Swift call to the external Tcl function f (f.swift):

(int o) f(int i1, int i2) "my_pkg" "0.0"
[ "set <<o>> [ my_pkg::f <<i1>> <<i2>> ]" ];

Then, build a simple Swift test script to try it (test-f.swift):

import io;
import f;

int x = 2;
int y = 3;
int z = f(x,y);
printf("sum: %i", z);

Now, make the package available to Turbine and run it:

$ stc -r $PWD test-f.swift test-f.tcl
$ turbine test-f.tcl
sum: 5

Conclusion: Now you can make many concurrent calls to a simple Tcl function using Swift.

3.2. Complete example 2: Simple C function

This section describes how to call C functions concurrently from Swift.

3.2.1. Establish the C library

Consider the following target C library header (g.h):

int g(int i1, int i2);

implemented with (g.c):

#include <stdio.h>
#include <unistd.h>

#include "g.h"

int g(int i1, int i2)
{
  int sum = i1+i2;
  printf("g: %i+%i=%i\n", i1, i2, sum);
  printf("sleeping for %i seconds...\n", sum);
  sleep(sum);
  return sum;
}

This function is normally called as (test-g.c):

#include "g.h"

main()
{
  g(2, 3);
}

It may be compiled and run with (test-g.sh):

#!/bin/sh -ex

gcc -c g.c
gcc -c test-g.c
gcc -o g.x test-g.o g.o

./g.x
> time ./g.x
g: 2+3=5
sleeping for 5 seconds...
./g.x  0.00s user 0.00s system 0% cpu 5.003 total

3.2.2. Export the C library as a Tcl package

Now, we would like to call many concurrent instances of g(). First, we need to make the Tcl package.

First, we run SWIG on the header:

swig -module g g.h

SWIG produces g_wrap.c. Compile this and create a Tcl package (test-swig-g.sh):

#!/bin/sh
rm -fv *.o
set -ex
gcc -c -fPIC -Wall g.c
gcc -c -fPIC -I/home/wozniak/sfw/tcl-8.6.0/include g_wrap.c
gcc -shared -o libg.so g_wrap.o g.o
tclsh make-package.tcl > pkgIndex.tcl

This produces libg.so and the Tcl package file pkgIndex.tcl. We test this from Tcl (test-g.tcl):

package require g 0.0
g 2 3

This is run from the shell as (test-g-tcl.sh):

> export TCLLIBPATH=$PWD
> tclsh test-g.tcl
g: 2+3=5
sleeping for 5 seconds...

3.2.3. Call the Tcl package from Swift (one-shot)

Then, we bring the Tcl package into Swift (test-g-1.swift):

import io;

@dispatch=WORKER
(int sum) g(int i1, int i2) "g" "0.0"
[ "set <<sum>> [ g <<i1>> <<i2>> ]" ];

int sum = g(2, 3);
printf("Swift: sum: %i", sum);

Compile and run (test-g-1.sh):

> stc test-g-1.swift test-g-1.tcl

# Point Turbine to the Tcl package for g
> export TURBINE_USER_LIB=$PWD
# Turn off logging
> export TURBINE_LOG=0

> time turbine test-g-1.tcl
turbine test-g-1.tcl  5.60s user 0.44s system 113% cpu 5.300 total

3.2.4. Call the Tcl package from Swift concurrently

Write a foreach loop around the call to g (test-g-n.swift):

import io;
import stats;


@dispatch=WORKER
(int sum) g(int i1, int i2) "g" "0.0"
  [ "set <<sum>> [ g <<i1>> <<i2>> ]" ];

int d[];
foreach i in [0:5] {
  d[i] = g(i, 5-i);
}

y = sum_integer(d);
printf("y: %i", y);

Compile and run (test-g-n.swift):

> stc test-g-n.swift test-g-n.tcl

# Point Turbine to the Tcl package for g
> export TURBINE_USER_LIB=$PWD
# Turn off logging
> export TURBINE_LOG=0

# Single worker mode:
> time turbine test-g-n.tcl
g: 0+5=5
sleeping for 5 seconds...
g: 1+4=5
...
g: 5+0=5
sleeping for 5 seconds...
turbine test-g-n.tcl  30.60s user 2.26s system 108% cpu 30.300 total

# Many worker mode:
> time turbine -n 7 test-g-n.tcl
...
turbine -n 7 test-g-n.tcl  7.30s user 0.56s system 146% cpu 5.375 total

Thus, providing 1 worker for each loop iteration yields perfect speedup. Note that we had to provide 7 MPI processes: one for each of the 6 loop iterations and one ADLB server.

3.3. Complete example 3: C function with binary data

In this case, we pass binary data in a byte array between Swift and C.

The application in this case consumes a binary file containing double precision floats. It manipulates the data in Swift and passes it to a C library. The C library returns the sum of the numbers as an array of double precision numbers of length 1.

3.3.1. Create the data set

First, we create our binary file using the provided turbine-write-doubles/turbine-read-doubles tools (create-data.sh):

> turbine-write-doubles input.data 1 2 3 10

# Check that it worked:
> du -b input.data
32      input.data
> od -t f8 input.data
0000000                        1                        2
0000020                        3                       10
0000040
> turbine-read-doubles input.data
1.0000
2.0000
3.0000
10.0000

Simple manipulation can be performed in Swift as (test-b-simple.swift):

import blob;
import io;

file data = input_file("input.data");
blob b = blob_read(data);
float v[] = floats_from_blob(b);
printf("size(v) = %i", size(v));
printf("v[0]=%0.2f", v[0]);
printf("v[last]=%0.2f", v[size(v)-1]);

test-b-simple.sh:

> stc test-b-simple.swift test-b-simple.tcl
> turbine test-b-simple.tcl
size(v) = 4
v[last]=10.00
v[0]=1.00

3.3.2. Define the C function

Our C function is (b.c):

#include <stdio.h>
#include <stdlib.h>

#include "b.h"

double* b(double* v, int length) {
  int i;
  double sum = 0.0;
  printf("length: %i\n", length);
  for (i = 0; i < length; i++) {
    sum += v[i];
  }
  printf("sum: %f\n", sum);
  double* result = malloc(sizeof(double));
  result[0] = sum;
  return result;
}

It may be tested from C with (test-b.c):

#include <stdlib.h>
#include "b.h"

main() {
  double v[4] = { 1, 2, 3, 10 };
  double* sum = b(v, 4);
  free(sum);
}

test-b.sh:

> gcc -c b.c
> gcc -c test-b.c
> gcc -o b.x test-b.o b.o
> ./b.x
length: 4
sum: 16.000000

3.3.3. Create the Tcl package

Build the Tcl package with (swig-b.sh):

rm *.o
swig -module b b.i
gcc -c -fPIC b.c
gcc -c -fPIC $TCL_INCLUDE_SPEC b_wrap.c
gcc -shared -o libb.so b_wrap.o b.o
tclsh make-package.tcl > pkgIndex.tcl

Since the blob pointer is of type void*, we need to wrap the C function in some helper Tcl code to transmit blob data to the typed C function. This is performed in b.tcl:

namespace eval b {
    # v is formatted as a Turbine blob a list of [ pointer length ]
    # The pointer is a simple Tcl integer
    # The length is the byte length
    proc b_tcl { v } {

        # Unpack the list
        set ptr [ lindex $v 0 ]
        set len [ lindex $v 1 ]

        # Get the number of numbers to sum
        set count [ expr $len / [ blobutils_sizeof_float ] ]

        # Convert the pointer number to a SWIG pointer
        set ptr [ blobutils_cast_int_to_dbl_ptr $ptr ]

        # Call the C function
        set s [ b $ptr $count ]

        # Pack result as a Turbine blob and return it
        set r [ blobutils_cast_to_int $s ]
        return [ list $r 8 ]
    }
}

Thus, Swift calls the Tcl wrapper, which calls the C function. Additional information about blobs is found in the Blob Utilities Guide.

Note that we return blob containing a pointer to freshly allocated memory to Swift. The system will release this memory.

3.3.4. Call from Swift

The Swift code to pass the blob to b() is as follows (test-b.swift):

import blob;
import io;

(blob sum) b(blob v) "b" "0.0"
[ "set <<sum>> [ b::b_tcl <<v>> ]" ];

file data = input_file("input.data");
blob v = blob_read(data);
blob s = b(v);
float sum[] = floats_from_blob(s);
printf("sum (swift): %f", sum[0]);

test-b-swift.sh:

> stc test-b.swift test-b.tcl
> turbine test-b.tcl
length: 4
sum: 16.000000
sum (swift): 16.000000

3.4. Complete example 4: Calling a Fortran function

In this example, we call Fortran from Swift. We do this by wrapping the Fortran function in a C++ wrapper by using FortWrap. Then, we wrap for Swift by using SWIG.

This example will demonstrate multiplying y=A*x using Swift to call a user Fortran library, which in turn calls the BLAS function dgemv. This demonstrates the generality of our model.

In these scripts, you must have the BLAS archive. Change the appropriate shell variable to point to this file.

leaf__1.png

3.4.1. Establish the Fortran function

Consider the Fortran function (mvm.f):

! Matrix-Vector Multiply: y = A*x via BLAS
      subroutine MVM(A, x, y, n)

      integer, intent(IN) :: n
      double precision, intent(IN)  :: A(n,n)
      double precision, intent(IN)  :: x(n)
      double precision, intent(OUT) :: y(n)

      double precision :: alpha, beta

      alpha = 1.0D0
      beta  = 0.0D0
      call dgemv('N', n, n, alpha, A, n, x, 1, beta, y, 1)

      end subroutine

A test program to run this is (test-mvm.f):

      program testMVM

      parameter (n = 2)
      double precision :: A(n,n)
      double precision :: x(n)
      double precision :: y(n)
      double precision :: alpha, beta

      open (unit=1, file='A.data', form='unformatted',
     $      access='direct', recl=n*n*8)
      read (1, rec=1) A
      close (1)

      open (unit=1, file='x.data', form='unformatted',
     $      access='direct', recl=n*8)
      read (1, rec=1) x
      close (1)

      do i = 1,n
         y(i) = 0.0D0
      end do

      call MVM(A, x, y, n)

      print *, "y"
      print *, y(1)
      print *, y(2)

      end program

It is built and run with (test-mvm.sh):

#!/bin/bash -eu

# Note Fortran memory layout:
turbine-write-doubles A.data 1 3 2 4
turbine-write-doubles x.data 5 6

gfortran -c mvm.f
gfortran -c test-mvm.f
gfortran -o test-mvm.x test-mvm.o mvm.o ${BLAS}

./test-mvm.x

FortWrap will scan mvm.f and produce the C++ files FortFuncs.h and FortFuncs.cpp, which we wrap with SWIG to produce a Tcl function called FortFuncs_MVM. We can call this from Swift.

3.4.2. Write the Swift code and Tcl wrapper

Our goal is to rewrite test-mvm.f in Swift so we can call MVM concurrently on many processors. Our Swift replacement is (test-mvm.swift):

import blob;
import io;

(blob y) mvm_blob(blob A, blob x, int n) "mvm" "0.0"
[ "set <<y>> [ mvm::mvm <<A>> <<x>> <<n>> ]" ];

int n = 2;
blob A_blob = blob_read(input_file("A.data"));
blob x_blob = blob_read(input_file("x.data"));
blob y_blob = mvm_blob(A_blob, x_blob, n);
float y[] = floats_from_blob(y_blob);
foreach v, i in y {
  printf("y[%i]=%f", i, v);
}

Our Tcl wrapper converts the Tcl call to mvm on blob arguments to the Tcl call to FortFuncs_MVM on pointers. This is performed similarly to the C example above (mvm.tcl).

3.4.3. Build and run

Now, we build everything (build.sh):

#!/bin/bash -eu

TURBINE=$( which turbine )
TURBINE_HOME=$( dirname $( dirname ${TURBINE} ) )
source ${TURBINE_HOME}/scripts/turbine-config.sh

# Wrap the Fortran in C++
fortwrap.py --array-as-ptr --no-vector --no-fmat mvm.f
# Wrap the C++ in Tcl
swig -c++ -module mvm FortFuncs.h
# Minor fix to the wrapper code
sed -i '11i#include "FortFuncs.h"' FortFuncs_wrap.cxx

# Compile everything
g++      -c -fPIC -I . FortFuncs.cpp
g++      -c -fPIC ${TCL_INCLUDE_SPEC} FortFuncs_wrap.cxx
gfortran -c -fPIC mvm.f

# Build the shared object
g++ -shared -o libmvm.so FortFuncs_wrap.o FortFuncs.o mvm.o ${BLAS} -lgfortran

# Make the Tcl package
${TCLSH} make-package.tcl > pkgIndex.tcl

We run it in Swift with (test-mvm-swift.sh):

#!/bin/sh

swift-t -r $PWD test-mvm.swift

This produces:

y[0]=17.000000
y[1]=39.000000

3.5. Complete example 5: Calling C with argc/argv

It may be desirable to call an existing C program from Swift with as little modification as possible. This example considers the case that the Swift program will construct an array of strings to pass to the C code. The C program is minimally modified by renaming its main() function and calling that from Swift/T.

Consider the user code (main.c):

#include <stdio.h>
#include "main.h"
int main(int argc, char* argv[]) {
  for (int i = 0; i < argc; i++)
    printf("arg[%i]: %s\n", i, argv[i]);
  return 0;
}

The function essentially acts like /bin/echo, reporting its arguments.

Consider the user code (main_leaf.c):

#include <stdio.h>
#include "main.h"
int main_leaf(int argc, char** argv) {
  for (int i = 0; i < argc; i++)
    printf("arg[%i]: %s\n", i, argv[i]);
  return 0;
}

This is essentially the same program except that its main() function has been renamed to swift_main(). Also, the header swift-main.h has been created to allow the Swift/T framework to call this code.

The Swift/T distribution comes with functionality to make this easy to call. The key program is genleaf.

The equivalent Swift code is as follows (test-main.swift):

import io;

mainapp;

printf("Swift...");
string A[] = [ "arg1", "arg2", "arg3" ];
rc = main_leaf(A);
printf("exit code: %i", rc);

The Swift version defines the extension function symbol swift_main, then calls it with the given array of strings. The "exit code" (actually just a return value) is available to Swift.

This example may be compiled and run (from C, Tcl, and Swift) by using the provided Makefile.

The above process is semi-automated by the genleaf script. The script takes a C program and a header file as input and produces the required Tcl-C extension and Tcl leaf function. It also produces a simple source Swift and stc-compiled Tcl code ready to be run via turbine. Invoke genleaf as follows (test-main.sh):

genleaf -vv main.c main.h test-main.swift
swift-t -r $PWD user-code.swift

Thus, main_leaf.c is generated by genleaf, creating an easy-to-use interface for Swift.

3.6. Complete example 6: Calling Fortran with argc/argv

It may be desirable to call an existing Fortran program from Swift with as little modification as possible. This example considers the case that the Swift program will construct an array of strings to pass to the Fortran code. The Fortran program is minimally modified by removing its "open code" and exposing a function that accepts an array of strings instead of using Fortran builtins command_argument_count() and get_command_argument().

Assume you wish to call the Fortran function func() (func.f90):

  subroutine func(argc, argv, output)

    implicit none

    integer, intent(in) :: argc
    type (string_array) :: argv
    double precision, intent(out) :: output

The argc/argv is stored in a string_array, defined in the top of func.f90.

This data structure is constructed and filled by the test program prog-f90.f90 as follows:

argc = command_argument_count()
call string_array_create(argv, argc)

do i = 1, argc
   call get_command_argument(i, tmp)
   call string_array_set(argv, i, tmp)
end do

To call this function from Swift, run ./build.sh. (You need to set the Tcl installation directory in build.sh.)

The Makefile proceeds as follows:

  1. We first wrap the Fortran with FortWrap to produce C++ wrappers. Note that FortWrap wraps the Fortran type string_array with a C++ class (an example of how to use the class directly from C++ is provided in prog-cxx.cxx).

  2. We wrap the generated C++ header (FortWrap.h) with SWIG.

  3. We compile everything.

  4. A Tcl function (func.tcl) is used to retrieve the string array from Swift and create an output buffer for the output double.

After the build, you can run the C++ or Fortran test programs to see how they work:

./prog-f90 a b c
...
./prog-cxx a b c

We call the function from Swift using the simple syntax:

+

(float v) func(string A[]) "f" "0.0"
[
  "set <<v>> [ func <<A>> ]"
];

We run with:

+

make
swift-t -r $PWD prog-swift.swift

4. Advanced mode

In this mode, the user provides the Swift/T runtime with dataflow information so that more complex operations may be performed.

4.1. Complete example 7: Leaf task with dataflow

When using the advanced syntax, the final argument is the name of a Tcl function:

(int o) f(int i) "my_pkg" "version" "f";

This function is called before i is set. Thus, f must issue a Turbine rule statement to schedule real leaf work when i is set.

Consider the following Tcl code (f.tcl):

package provide my_pkg 0.0

namespace eval my_pkg {

    # Function called by Swift/T
    proc f { outputs inputs } {
        set x [ lindex $inputs  0 ]
        set y [ lindex $inputs  1 ]
        set z [ lindex $outputs 0 ]
        rule [ list $x $y ] "my_pkg::f_body $z $x $y" \
            type $turbine::WORK
    }

    # Function called by Swift/T rule
    proc f_body { z x y } {
        set x_value [ retrieve_integer $x ]
        set y_value [ retrieve_integer $y ]
        set z_value [ f_impl $x_value $y_value ]
        store_integer $z $z_value
    }

    # Real Tcl implementation: does actual work
    proc f_impl { x_value y_value } {
        return [ expr $x_value + $y_value ]
    }
}

In this case, Tcl function f_impl is the implementation: the actual code the user desires to call. This may be an existing Tcl routine or linkage to native code as described above. It consumes two Tcl integers and returns their sum as a Tcl integer.

Two function, f and f_body, are required to connect this to Swift/T. f is the interface: it is called by Swift/T when the call is issued but x and y may not yet have values. The Tcl variables x and y are the Turbine Datum (TD) identifiers, not the values, of Swift variables x and y. The outputs are packed in Tcl list outputs and the inputs are in Tcl list inputs.

When x and y are stored elsewhere, the Swift/T runtime evaluates the action item in the rule: "my_pkg::f_body $z $x $y". Thus, f_body receives the TDs for z, x, and y. It retrieves the values of x and y, calls f_impl to compute the value for z, and stores it. Now, any Swift/T statements (rules) dependendent on z will be executed, and so on.

This Tcl code is called by the following simple Swift/T test (test-f.swift):

import io;

(int o) f(int i1, int i2) "my_pkg" "0.0" "f";

int x = 2;
int y = 3;
int z = f(x,y);
printf("sum: %i", z);

For more information about the APIs used to store/retrieve Swift/T data and issue rules, see Turbine Internals.

The interface/body/implementation pattern is used by much of the Swift/T library. The source of this library is found in the Turbine installation directory. The Swift headers are in turbine/export and their Tcl implementations are in turbine/lib. This is a good way to find out what else may be done.

4.2. Complete example 8: Calling an MPI library

Calling an existing MPI library is similar to calling any other C code. In this example, we will show how to obtain the new communicator from Swift/T. This functionality only works when using Swift/T on an MPI-3 implementation such as MPICH 3.0.

4.2.1. Identify the function

Consider the following MPI library function (f.c):

double
f(MPI_Comm comm, int k)
{
  printf("f()\n");
  int task_rank, task_size;
  MPI_Comm_rank(comm, &task_rank);
  MPI_Comm_size(comm, &task_size);
  printf("In f(): rank: %i/%i\n", task_rank, task_size);
  MPI_Barrier(comm);
  sleep(task_rank);
  MPI_Barrier(comm);
  MPI_Comm_free(&comm);
  if (task_rank == 0)
    // Return a real value
    return sin(k+task_size);
  // Return a placeholder
  return 0.0;
}

This function accepts an MPI communicator comm freshly created for the task. It also accepts user data k and must return a double. Rank 0 in the task communicator returns the real value- the other ranks may cooperate in the computation but do not interact with the data store.

Communication on comm is isolated from Swift/T implementation communication. Point-to-point messaging and collectives (such as MPI_Barrier(), as shown) are allowed.

We desire to call this from Swift as (test-f.swift):

import io;

@par @dispatch=WORKER (float z) f(int k) "f" "0.0" "f_tcl";

float z = @par=2 f(3);
printf("z: %0.3f", z);

In the declaration, @par allows us to call the function as a parallel function. At call time, we use @par=2.

We wrap this function with SWIG and a Swift/T interface/body:

namespace eval f {

    proc f_tcl { outputs inputs args } {
        set z [ lindex $outputs 0 ]
        set k [ lindex $inputs 0 ]
        rule $k "f::f_tcl_body $z $k" {*}$args type $turbine::WORK
    }

    proc f_tcl_body { z k } {
        # Retrieve k
        set k_value [ retrieve_integer $k ]
        # Look up MPI information
        set comm [ turbine::c::task_comm ]
        set rank [ adlb::rank $comm ]
        # Run the user code
        set z_value [ f $comm $k_value ]
        # Store result
        if { $rank == 0 } {
            store_float $z $z_value
        }
    }
}

This differs from the single-process task (example 5) in that it passes the additional parallelism information in variable args into the rule statement so that Swift/T will allocate 2 workers for the task.

By convention, only rank 0 contacts the data store to store result z. If multiple processes write to z, a run-time error will result.

At run time, ensure Turbine has enough workers to run the user task:

> turbine -n 4 test-f.tcl
z: -0.959

5. Calling Swift/T from native code

5.1. Complete example 9: Calling Swift/T from C

Swift/T itself may be called from native code as a library.

5.1.1. Compilation

The Turbine installation directory contains the turbine.h header and the libtclturbine.so shared object. See build.sh for the example compiler commands.

5.1.2. Call

The call to Swift/T requires the MPI communicator in which to run, the compiled Swift/T program to run, a C-formatted argc/argv, and an output pointer.

The C call to Swift/T is written as (controller.c):

turbine_code rc = turbine_run(comm, "test-f.tcl", argc, argv, output);

where argc/argv are C-style arguments, and output is the buffer to hold the output.

The Swift script is a normal Swift script except for the call to turbine_run_output_blob(), which returns data to the controller (test-f.swift):

import blob;
import io;
import sys;

printf("args: %s", args());
string s = "MY_OUTPUT";
blob b = blob_from_string(s);
turbine_run_output_blob(b);

The controller MPI program is run as: (run.sh):

#!/bin/sh -e

./build.sh

mpiexec -n 4 ./controller.x

producing:

args: howdy ok bye
controller: output: MY_OUTPUT

Note that only rank 0 in the Swift/T communicator has access to the output data.