2. Programming with Athapascan-0

An Athapascan-0 parallel machine is a set of nodes. A node is implemented by a process and a physical computer can execute many of them. All executable modules of the nodes' processes can be the same or be different from each other.

The indication of the number of nodes necessary to a given parallel processor, their allocation to processes and the loading of the executable modules are not part of Athapascan-0. All that must be provided by the underlying process communication environment. The current implementation uses MPI, which imposes an SPMD model (Single Program Multiple Data), where all nodes execute the same module.

2.1. Athapascan-0 Objects

The execution of an Athapascan-0 program involves many objects implementing the aforementioned concepts. Some of these objects exist at startup time, others must be explicitly created and destroyed. Some objects also require an uniform naming across all nodes. The Athapascan-0 objects are:

2.2. Main Module and Startup

The executable module of every node starts executing the main() function of its C program. The first Athapascan-0 function call must be a0Init(), followed by some other Athapascan-0 function calls, and then a0InitCommit(). This two-phase initialization procedure allows the programmer to safely initialize critic objects. This procedure guarantees across all nodes a coherent naming scheme of the objects.

All the services must be declared between a0Init() and a0InitCommit(), in such a way they are all performed in the same order in all nodes. The ports that are supposed to be created identical in all nodes must also be declared there.

#include <ath0.b>

/* a service and its function */
int Serv1;
a0tError FuncServ1(void *data)
{  ...
}

/* another service and its function */
int Serv2;
a0tError FuncServ2(void *data)
{  ...
}

int main(int argc, char *argv[])
{
  a0Init(&argc, &argv);
  a0NewService(&Serv1, FuncServ1, ...);
  a0NewService(&Serv2, FuncServ2, ...);
  a0InitCommit();
  ...
  a0Terminate();
}

Athapascan-0 is not usable before executing a0Init().

After executing a0Init(), all nodes are ready to initialize the static Athapascan-0 objects. In this phase, all library module initialization functions can be called (see next sections). Declaration of service handlers and port creations are valid Athapascan-0 operators in this step. Global variables of the module can be initialized and synchronization objects like semaphores or mutexes can be created either during or after the initialization phase. Thread creation or communication are not allowed before a0InitCommit().

When the execution starts, only one thread is running on each module. This thread is called the main thread, as it executes the main() function. When the main thread execution reaches a0InitCommit(), the executing modules can use the full Athapascan-0 operator set.

After finishing all computations, at the end of the program, all the nodes must call a0Terminate(). When all nodes reach a0Terminate(), they stop execution and therefore the whole parallel program terminates. It is strongly encouraged that a0Terminate() be executed only by the main thread, because exit() is called after the return of its function, which would abnormally terminate the Athapascan-0 program. Though, at any point of execution, the parallel program may be canceled by a a0Abort(), after an user-detected irrecoverable error.

The variables a0SelfNode and a0NodeCount contain the local node identification number and the total number of nodes available in the parallel machine, respectively. This variables can be used to modify the behavior of the program, depending on the current node and on different quantities of nodes.

2.3. Program Arguments Parsing

The address of the arguments argc and argv of the main() function must be given to the a0Init() function. Athapascan-0 extracts from the program's arguments its own parameters and those of the underlying layers. This extraction is done on a reserved keyword basis and all options that are interpreted by Athapascan-0 or the underlying layers cannot be seen by the user. Although all the Athapascan-0 options start with an -a0 prefix, no assumption is made about the underlying layers' options.

a0run myprogram -n 4 -a0stack 10000 test -mypar 54

In the above example command line, the arguments -n 4 will be extracted by the underlying layers, in order to run four copies of the program in the parallel machine. The arguments -a0stack 10000 will be extracted by Athapascan-0 and every copy of the program myprogram will have test, -mypar and 54 as arguments.

2.4. Descriptor Allocation

All descriptors defined in an Athapascan-0 program (for threads, ports, requests, semaphores, etc.) have to be previously allocated in the memory of the executing module. All the functions that define new objects expect to receive a pointer to the object to be defined, which is just ``filled in''.

a0tBuffer *create_buffer(long size)
{
  a0tBuffer *new;

  new = (a0tBuffer *) malloc(sizeof(a0tBuffer));
  a0NewBuffer(new, A0SendBufferType, size);
  return new;
}

The use of automatic1 variables within a function, should be used as a descriptor if, and only if, they are created, used and destroyed in the same function, because its allocation space disappears after the end of the function.

void send(...)
{
  a0tBuffer buff;

  a0NewBuffer(&buff, ...);
  a0Pack(&buff, ...);
  ...
  a0SendBuffer(..., &buff);
  a0DisposeBuffer(&buff);
}

2.5. Library Modules

The implementation of an initialization function is encouraged, when developing Athapascan-0 user libraries. Each module can provide different services and global variables should be associated to them. All the services provided by a module of a library should have their declaration in the initialization function.

/**** module fourier.c, a library module ****/
#include <ath0.b>

/* exported services */
a0tService  ServFourier1;
a0tService  ServFourier2;

/* service functions */
static a0tError FuncFourier1()
{  ...
}
static a0tError FuncFourier2()
{  ...
}

/* module initialization */
void InitFourier()
{
     a0NewService(&ServFourier1, FuncFourier1, ...);
     a0NewService(&ServFourier2, FuncFourier2, ...);
}

Using an initialization function, a program that uses services of some library modules can execute all the initialization functions of all library modules in the same order. This guarantees that all services and ports created by those modules will receive the same identifiers.

/***** program who uses fourier library *****/
#include <ath0.b>
#include <fourier.h>

int main(int argc, char *argv[])
{
  a0Init(&argc, &argv);
  InitFourier();
  ...
  a0InitCommit();
  ...
}


1 defined with auto in C