12. Annex B: LOFT Reference Manual

12. Annex B: LOFT Reference Manual

Browsing

Home:
Concurrent Programming with Fair Threads
The LOFT Language

Previous chapter: Annex A: API of FairThreads
Next chapter: Annex C: the LOFT Compiler

Annex B: LOFT Reference Manual

12.1 Modules
  12.1.1 Extern Modules
  12.1.2 Main Module
12.2 Variables
  12.2.1 Types of Local Variables and Parameters
  12.2.2 Access to Local Variables and Parameters
12.3 Threads
  12.3.1 Thread Creation
  12.3.2 Thread State
  12.3.3 Thread Execution
  12.3.4 Return Code and Self
  12.3.5 Thread Deallocation
12.4 Schedulers
  12.4.1 Instants
  12.4.2 Scheduler Deallocation
12.5 Events
  12.5.1 Generate
  12.5.2 Event Deallocation
12.6 Instructions
12.7 Binding Instructions
  12.7.1 Unlink
  12.7.2 Link
12.8 Atomic Instructions
  12.8.1 Stop
  12.8.2 Suspend
  12.8.3 Resume
12.9 Non-atomic Instructions
  12.9.1 Cooperate
  12.9.2 Halt
  12.9.3 Return
  12.9.4 Await
  12.9.5 Join
  12.9.6 Get Value
  12.9.7 If
  12.9.8 While
  12.9.9 Repeat
  12.9.10 Run

Chapters

1. Introduction
2. The Language LOFT
3. Examples - 1
4. Programming Style
5. Semantics
6. FairThreads in C
7. Implementations
8. Examples - 2
9. Related Work
10. Conclusion
11. Annex A: API of FairThreads
12. Annex B: LOFT Reference Manual
13. Annex C: the LOFT Compiler
  Glossary
  Index

Loft is an extension of C for concurrent and parallel programming. Basic units of concurrency are threads which are created as instances of modules and are run under the control of schedulers. Threads can synchronize and communicate using broadcast events. Threads linked to the same scheduler run in a cooperative way, leading to deterministic systems. Threads can dynamically change their linking to schedulers. Moreover, special native threads have the possibility to stay unlinked from any scheduler, becoming then autonomous. Unlinked threads can be executed in real parallelism.
12.1 Modules

Loft code is a mix of standard C code and of modules. Loft code is first preprocessed (typically by the gcc -E command), then translated into C, and finally compiled by the C compiler in order to get executable code. Modules are templates from which threads, called instances, are defined. Module definitions start whith the keyword module and terminate with end module. The syntax of modules is:

module_definition: 
   module kind module_name (params)
   locals
   inst
   final
   end module
A module is native when the keyword native follows module. Native modules should only be used in programs run by a preemptive operating system (Linux for example). Instances of a native module must be implemented as kernel threads. Instances of non-native module do not need specific kernel threads to execute (they can use the one of the scheduler to which they are linked).
kind: native | /*empty*/
The module name is an identifier or the keyword main (this case is considered in section Main Module).
module_name: identifier | main
Parameters in params and variables defined in locals are considered in section Variables. The body inst of the module is an instruction (usually, a sequence of instructions) which is executed by the threads instances of the module. Instructions are considered in section Instructions. The finalizer is an optional atomic instruction which is executed in case of forced termination; the executing thread of finalizers is left unspecified.
finalizer: finalizer atomic | /*empty*/

12.1.1 Extern Modules

Modules defined in others files, or later in the same file, can be declared using clauses of the form:
extern_modules: module identifier, ... , identifier ;

12.1.2 Main Module

A program containing a module named main can be directly executed. The parameters of main must follow the argc/argv conventions of C. The translation of the main module, first creates a new instance of the module and an implicit scheduler, then adds the new thread in the implicit scheduler, and finally makes the implicit scheduler react cyclically. When no main module is defined, a function C with the name main must be defined, as usual in C. In this case, it must call the function loft_initialize in order to create the implicit scheduler.
loft_initialize: loft_initialize () ;
12.2 Variables

There are 3 distinct kinds of variables:

  1. Global variables declared outside module definitions. These variable are the standard global variables of C.
  2. Automatic variables declared in blocks of C instructions (of the form {...}). These variables can appear in declarations of C functions, or in atomic instructions, in module definitions. Automatic variables are allocated when the control enters the block in which they are declared, and destroyed when the block is left.
  3. Parameters and local variables declared in modules. Each instance of a module has its own copy of these variables. Their values are preserved during the passing of instants.
Thus, concerning memory management, Loft basically differs from C by the presence of variables of kind 3, which are local to threads and maintain their value between instants.

12.2.1 Types of Local Variables and Parameters

Types of parameters and local variables can be:
  • Basic types (for example, int).
  • Named types (defined using typedef).
  • Pointer or double pointer types on the previous types.
params: defvar, ..., defvar | /*empty*/
defvar: 
    type identifier
|   type * identifier
|   type * * identifier
type: basic_type | identifier
locals: local defvar, ... , defvar | /*empty*/ ;
Remark: the restriction on the types of parameters and local variables is introduced to simplify the implementation and should be removed in future versions of the language.

12.2.2 Access to Local Variables and Parameters

Local variables and parameters must always be explicitly tagged by the keyword local to distinguish them from global or automatic variable with same names: all uses of a local variable or parameter x must be of the form local(x).
12.3 Threads

Threads are concurrent entities created from modules and run by schedulers. Threads are of the type thread_t.

12.3.1 Thread Creation

New threads instances of a module m are returned by the C function m_create which has the same parameters as m. The new threads are linked to the implicit scheduler. With the function m_create_in, new created threads are linked to a scheduler given as first parameter (the other parameters are passed to the thread in the standard way).
thread_creation: 
   identifier_create (exp, ... , exp)
|  identifier_create_in (exp, exp, ... , exp)
Threads linked to a scheduler during one instant start running only at the beginning of the next instant.

12.3.2 Thread State

The state of a thread is composed of two parts: the execution state and the binding state. The possible values for the execution state of a thread are:
  • Ready to execute.
  • Terminated. A thread becomes terminated when it has finished to execute its body. A thread can also be forced to terminate when it is stopped.
  • Suspended. A suspended thread must be resumed before it can continue to execute.
The possible values for the binding state are:
  • Linked to a scheduler. When linked to a scheduler, a thread must cooperate with the others threads linked to the same scheduler, and its execution is under the control of the scheduler.
  • Unlinked. This is only possible for instances of native modules. Unlinked threads are autonomous and run at their own pace.
Initially, that is when created, a thread is ready to execute and linked to the scheduler in which it is created.

12.3.3 Thread Execution

Unlinked threads are supposed to be executed by kernel threads. An unlinked thread run autonomously, until it reaches a link instruction which changes its linking state. A linked thread resumes execution of its body when it receives the control from the scheduler to which it is linked. As the scheduling is cooperative, the resumption is always supposed to finish after a finite delay. At the end of the resumption, the thread returns the control back to the scheduler and signals it with one of the following values:
  • Terminated. The thread terminates when it completes the last instructions it has to run.
  • Blocked. The thread is blocked either because it is awaiting an unknown event (await), or because it tries to get an event value which is not available (get_value).
  • Cooperating. The thread is not terminated but has finished to execute for the current instant.

12.3.4 Return Code and Self

Threads have a return code set during the execution of certain non-atomic instructions (namely, await, join, and get_value). The return code can be OK, ETIMEOUT, or ENEXT. The return code of the executing thread, set by the last executed non-atomic instruction, is returned by the return_code function:
thread_return_code: return_code ()
The executing thread is returned by the self function:
self: self ()

12.3.5 Thread Deallocation

The memory allocated to a thread can be recovered using the thread_deallocate function:
thread_deallocation: thread_deallocate (exp) ;
This is the programmers's responsability to perform correct deallocation of threads.
12.4 Schedulers

A scheduler defines instants during which all ready threads linked to it are run in a cooperative way. Schedulers are of type scheduler_t. New schedulers can be created with the function scheduler_create:

scheduler_creation: scheduler_create ()
An implicit scheduler should exist in all program. It is returned by the function implicit_scheduler. During execution, a linked thread can have access to the current scheduler running it, with the function current_scheduler.
scheduler_access: 
   implicit_scheduler ()
|  current_scheduler ()

12.4.1 Instants

A phase of a scheduler consists in giving the control in turn to all the ready threads which are linked to it. The order in which the threads receive the control is the order in which they have been linked to the scheduler. During an instant, the scheduler executes cyclically a serie of phases, up to a situation where no thread remains blocked. Then, the scheduler decides that the current instant is finished, and it can then proceed to the next instant. Thus, at the end of each instant, all ready threads either have terminated or have cooperated. The scheduler decides that the current instant is finished when, at the end of a phase, there is no possibility for any blocked thread to progress: no awaited event has been generated, and no new value has been produced which could unblock a thread trying to get it. One instant of a scheduler is executed by the scheduler_react function.
scheduler_react: scheduler_react (exp) ;
In scheduler_react(exp), exp should evaluate to a scheduler previously created by scheduler_create.

12.4.2 Scheduler Deallocation

The memory allocated to a scheduler can be recovered using the scheduler_deallocate function:
scheduler_deallocation: scheduler_deallocate (exp) ;
This is the programmers's responsability to perform correct deallocation of schedulers.
12.5 Events

Events are used by thread to synchronize and to communicate. Events are of type event_t. An event is always created in a specific scheduler which is in charge of it during all its lifetime. The function event_create returns an event created in the implicit scheduler. The function event_create_in returns an event created in the scheduler in argument.

event_creation: 
   event_create ()
|  event_create_in (exp)
At each instant, events have a presence status which can be present, absent, or unknown. All events managed by a scheduler are automatically reset to unknown at the beginning of each new instant (thus, events are non-remanent data). All events which are unknown become absent when the scheduler decides to terminate the current instant (see Instants).

12.5.1 Generate

There are two ways for an event to be present during one instant:
  • There were an order to generate it received by the scheduler during execution of the previous instant and coming from a thread which is not linked to the scheduler.
  • It is generated during the current instant by one of the threads linked to the scheduler.
One can associate values to generations of events. All values associated to the generations of the same event during one instant are collected in a list, as they are produced. They are available only during the current instant, using the get_value instruction (Get Value).
generate: 
|  generate (exp) ;
|  generate_value (exp, exp) ;
The execution of generate(exp) starts by the evaluation of exp which should return an event e. If the executing thread is unlinked or if the scheduler sched in charge of e (the one in which e has been created) is different from the one of the thread, then the order is sent to sched to generate e at the beginning of its next instant. Otherwise, e is immediately generated in sched. The execution of generate(exp,exp) starts by the evaluation of the two expressions in argument. The first one should return an event e, and the second one a value v of a pointer type (void*). The execution is the same as the one of the previous call, except that v is added as last element to the list of values associated to e at the instant where e is generated.

12.5.2 Event Deallocation

The memory allocated to an event can be recovered using the event_deallocate function:
event_deallocation: event_deallocate (exp) ;
This is the programmers's responsability to perform correct deallocation of events.
12.6 Instructions

Instructions are binding instructions, atomic instructions, non-atomic instruction, or sequences of instructions:

inst: 
   bind
|  atomic
|  non_atomic
|  inst ... inst
A thread starts to execute a component of a sequence as soon as the execution of the previous component is completed.
12.7 Binding Instructions

There are two binding instructions:

bind: 
   unlink ;
|  link (exp) ;

12.7.1 Unlink

The unlink instructions should only appear in native modules. It has no effect if the executing thread is already unlinked. Otherwise, the thread executing the instruction returns back the control to the scheduler, signaling it that it cooperates. In this case, the thread is removed from the scheduler which thus will never consider it again (except, of course, if the thread re-links to it later). As soon as the thread has returned the control back to the scheduler, it starts running in an autonomous way.

12.7.2 Link

A thread executing link(exp) first evaluate exp which should return a scheduler sched. If the thread is already linked, it unlinks from the scheduler to which it is linked, and then waits for sched to give it the control. If the thread is unlinked, it just waits for the control from sched. In all cases, sched will resume execution of the thread, as it does for new created thread, at the beginning of the next instant.
12.8 Atomic Instructions

Atomic instructions have the form of blocks of C code or of C function calls. They can be executed by linked and unlinked threads, with the same semantics, which is actually the one they have in standard C. However, when executed by a linked thread, execution of an atomic instruction is instantaneous, which means that it terminates in the same instant it is started.

atomic: 
   {c-code}
|  identifier (exp,..., exp) ;
|  order
The previous calls of functions to create and manage threads, schedulers, and events are considered as special cases of atomic instructions. Orders are given to schedulers to stop, suspend, or resume threads:
orders: 
   stop (exp) ;
|  suspend (exp) ;
|  resume (exp) ;
All orders received by a scheduler during one instant are systematically processed at the beginning of the next instant (to avoid interference with executing threads) in the order in which they are issued.

12.8.1 Stop

The execution of stop(exp) starts by the evaluation of exp which should return a thread th. The effect of the call is undefined if th is unlinked. Otherwise, the order to terminate th is sent to the scheduler to which it is linked.

12.8.2 Suspend

The execution of suspend(exp) starts by the evaluation of exp which should return a thread th. The effect of the call is undefined if th is unlinked. Otherwise, the order to suspend th is sent to the scheduler to which it is linked.

12.8.3 Resume

The execution of suspend(exp) starts by the evaluation of exp which should return a thread th. The effect of the call is undefined if th is unlinked. Otherwise, the order to resume th is sent to the scheduler to which it is linked. Resuming a thread which is not suspended has no effect.
12.9 Non-atomic Instructions

Non-atomic instructions should only be executed by linked threads, and their semantics is undefined when they are executed by unlinked threads. Execution of non-atomic instructions can take several instants to complete.

non_atomic: 
   cooperate;
|  halt;
|  return;
|  await (exp);
|  await (exp, exp);
|  join (exp);
|  join (exp, exp);
|  get_value (exp, exp, variable);
|  if (exp) then_branch else_branch end
|  while (exp) do inst end
|  repeat (exp) do inst end
|  run identifier (exp, ..., exp);
then_branch: then inst | /*empty*/
else_branch: else inst | /*empty*/

12.9.1 Cooperate

A thread executing cooperate returns the control to the scheduler, signaling it that it cooperates. If the thread regains control in the future, it will resume execution in sequence of the cooperate instruction.

12.9.2 Halt

A thread executing halt returns the control to the scheduler, signaling it that it cooperates. If the thread regains control in the future, it will re-execute the same halt instruction. Thus, halt blocks the thread forever, without never terminating it.

12.9.3 Return

A thread executing return returns the control to the scheduler, signaling it that it terminates. Thus, it will never regain control again from the scheduler.

12.9.4 Await

A thread executing await(exp) first evaluates exp. The expression should evaluate to an event e. If e is present, then the thread proceeds in sequence of the await instruction. Otherwise, if the current instant is finished (which means that e is absent), then the thread returns the control to the scheduler, signaling it that it cooperates. If the thread regains control in some future instant, execution will restart at the same place, waiting for the same event e. If the current instant is not finished, then the thread returns the control to the scheduler, signaling it that it is blocked. When the thread will regain the control, it will, as previously, continue to test the presence of e. A thread executing await(exp,exp) first evaluates the two arguments. The first one should evaluate to an event e, and the second one to an integer value k. Then, the thread behaves as the previous instruction, but the waiting of e is limited to at most k instants. If e is generated during the next k next instants, then the return code of the thread is set to OK. Otherwise, if e is still absent at the end of the kth instant, then the thread returns the control to the scheduler, signaling it that it cooperates ; moreover, the return code of the thread is set to ETIMEOUT. In this case, the thread will proceed in sequence when receiving the control back (just as the cooperate instruction does).

12.9.5 Join

A thread executing join(exp) first evaluates exp. The expression should evaluate to a thread th. If th is terminated, then the thread proceeds in sequence of the join instruction. Otherwise, the thread behaves as if it was awaiting a special event generated by the termination of th. A thread executing join(exp,exp) first evaluates the two arguments. The first one should evaluate to a thread th, and the second one to an integer value k. Then, the thread behaves as the previous instruction, but the waiting of the termination of th is limited to at most k instants. The return code of the thread is set to ETIMEOUT if the limit is reached, and it is set to OK otherwise.

12.9.6 Get Value

A thread executing get_value(exp,exp,var) first evaluates the two expressions in arguments. The first should return an event e, and the second an integer k. Then, the thread tests if there are at least k values generated for e during the current instant. If it is the case, the kth value is assigned to var, the return code of the thread is set to OK, and the thread proceeds in sequence. Otherwise, if the current instant is not finished, then the thread returns the control to the scheduler, signaling it that it is blocked. When the thread will regain the control, it will, as previously, continue to test the existence of the kth value. If the current instant is finished, then the return code of the thread is set to ENEXT, and the thread returns the control to the scheduler, signaling it that it cooperates. When the thread will regain control, it will proceed in sequence (just as a cooperate instruction would do).

12.9.7 If

A thread executing if(exp)then i else j end first evaluates exp. If the evaluation returns true (a non-zero value) then the thread switches to i, else it switches to j. If the chosen branch is empty, the thread proceeds in sequence immediately.

12.9.8 While

A thread executing while(exp)do i end first evaluates exp. If the evaluation returns false (zero) then the thread proceeds in sequence of the loop. Otherwise, the thread behaves as if it was executing i, with one difference: when execution of i terminates, then exp is re-evaluated and the same process restarts: execution is left if evaluation returns false, and otherwise i is re-executed. Thus, the thread cyclically executes i while exp is true, exp being evaluated at the first instant and, after that, only when i terminates.

12.9.9 Repeat

thread executing repeat(exp)do i end first evaluates exp. The evaluation should return an integer value k. If k is negative or equal to 0, then the thread proceeds in sequence of the loop. Otherwise, the thread behaves as if it was executing a sequence of k instructions i.

12.9.10 Run

A thread th executing run m(e1,...,en) first creates a new instance of the module m with e1,...,en as arguments, and then joins the new created thread. Moreover, the new created thread is stopped if th is.

This page has been generated by Scribe.
Last update Wed Oct 22 18:41:04 2003