This project is read-only.

Section C) The RTFM-cOOre language

The RTFM-cOOre language has been designed to fulfil the following goals:
  • Providing a light-weight OO model for concurrent programming
  • Ease of analysis, allowing for fully static and specialized implementation
  • Ease of extension, w.r.t
    • inheritance (currently no inheritance)
    • type systems (currently untyped)
    • functional expression layer (currently imperative)
    • etc.,

In the following we will describe the syntax and semantics of RTFM-cOOre language by a set of examples. Currently the language is limited to the following grammar:

C.1) Syntax (subset in EBNF like form)

coore: 
  | classDef*
        
classDef:
  | "class" ID "<" classArgs ">" "{" classDecl* "}"              
  
classArgs:
  | classArg ("," classArgs)*
    
classArg:
  | pType ID                                                 
  | pType "(" mSig ")" ID                                     
    
mSig:
  | pType ("," mSig)*                      
  
classDecl:
  | pType ID ":=" expr ";"   
  | ID "<" params ">" ID  ";"
  | pType ID "(" mArgs ")" "{" stmt* "}" 
  | "Task" ID "(" mArgs ")" "{" stmt* "}"
  | "Reset" "{" stmt* "}"                                      
  | "Idle" "{" stmt* "}"                                      
    
mArgs:
  | mArg ("," mArgs)*
  
mArg:
  | pType ID                                                
    
pType:
  | INT | CHAR | BOOL | BYTE | VOID                                                       
     
params:
  | expr ("," params)*                        
    
expr:                                                        
  | "async" after? before? ids "(" params ")"    (* async expression *)
  | ids LP params RP                             (* id expression    *)
  | ids LP params RP                             (* sync expresion   *)
  | INTVAL                                       (* integer value    *)
  | CHARVAL                                      (* character value  *)
  | BOOLVAL                                      (* boolean value    *)
  | "RT_rand" "(" expr ")"                       (* RT built ins     *)
  | "RT_getc" "(" ")"                                            

ids:
  | ID "." ID                                                
  | ID                                                       

after:
  | "after" time                              

before:
  | "before" time          

time:
  | INTVAL tunit?

tuint:
  | "us" | "ms" | "s"   


As seen the language currenlty provides a simpleistic expression layer (without any operations besides a few built ins as a proof of concept). Also the set of statements is very limited (no conditionals, etc.)

Extending the -cOOre language with a resonable set of operations (on primitive types), and conditionals, along with approprite type checking will be the challenge to student of the Compiler Construction course during the period September 1st till November 1st 2014.

However, already today the -cOOre languge is operational and the principles can be demonstrated, as shown in the following set of examples.

The examples are design such to match those we visited in Section A) and basically replicate the same (or similar) funcitonality in -cOOre.

Let us start by looking at the CEx1.coore:

// CEx1.coore
// Per Lindgren (C) 2014
//
// Simple example

class Root<> { 
    Task t1 () {
        RT_printf("task1\n");
    }      

    Reset {
        RT_printf("User reset\n");
        async t1 ();
    } 
}


Any meaningful -cOOre program must give a Root class declaration, defining
either Reset or Idle (or both).

Currenlty Tasks can only be defined within the context of a class defintion,
to this end we use the Root class for the defintion of the Task t.

Conceptually an instance (object) of the Root class (and all its inner objects)
is created by the envirnoment when the system is "born". From an implementation
standpoint, this is done aleady by the the rtfm-core compiler, which fully
instantiates and specialises the generated -core code. Hence no objects are
created during execution of the system!

The program is complete and can be compiled (rtfm-coore->rtfm-core->gcc ...)
giving us an executable.

Let us look at the next example CEx2.coore:

// CEx2.coore
// Per Lindgren (C) 2014
//
// Simple example

class Root<> { 
    Task t1 (char c) {
        RT_printf("t1 : you entered : %c\n", c);
        async after 5 s before 100 ms t2(c); // we have no operations on char yet
    }      
    
    Task t2 (char c) {
        RT_printf("t2: the same character is %c\n", c);
    }

    Reset {
        RT_printf("User reset\n");
    }
     
    Idle {
        RT_printf("enter a character (before 5s)\n");
        char c := RT_getc();
        async before 5 s t1 (c);
    }
}


Here we demonstrate the use of Reset (for setup) and Idle (for background job(s) during run-time).

Touching on the semantics of -cOOre, each object (instance) is associated with an unique resoure. Each Tasks, methods (for Root also Idle) execute as a critical section (claiming the resource). This gives us:
  • Race free execution in a concurrent setting (similar to sychronize in Java).
  • Serialized execution, allowing the observable behavior (ordering of side-effects to be precisley defined in the program model).

Since Idle and the task t1 belongs to the same object, they may and will not run concurrenlty. That is fine in this specific case, but may not in others.

Concurrency can always be ensured as long as the sender and receiver belongs to different object (instances), as we will see in the next example, CEx3.coore:

// CEx3.coore
// Per Lindgren (C) 2014
//
// Multiple objects
class R<> {
    int x := 0;
    int y := 0;
    
    Task t1 () {
        RT_printf("task1 R claimed, point is (%d,%d)\n", x, y);
    }
    
    Task t2 () {
        x := 1;
        RT_printf("task2 R claimed\n");
        RT_sleep(5);
        y := 1;
        RT_printf("task2 R released\n");
    }      
}

class Root<> { 
    R<> r;
    Reset {
        RT_printf("User reset\n");
        async before 9 s r.t2();
        async after 1 s before 10 s r.t1();
    } 
}


Where as shared data in the -core language (and typical thread/task libraries) has to be manually protected, the object structure or -cOOre gives automatic protection.

The instance variables (x,y) are given their intiatial values directly in the object instation (in fact statically by the compiler).

The same timing semantics as for -core applies to the -cOOre program.

Let look a bit closer at object instantiation, CEx3.coore:

// CEx4.coore
// Per Lindgren (C) 2014
//
// Static communication (callback) structure
class R<int ri> {
    void fun(int i) {
        RT_printf("task%d R claimed : R.ri = %d\n", i, ri);
        RT_sleep(5);
        RT_printf("task%d R released\n", i);        
    }
     
    Task rt () {
        RT_printf("task rt R.ri = %d \n", ri);
    } 
}

class T<int i, void (int) fun> {
    Task tt () {
        RT_printf("task%d\n", i);
        fun(i);
    }
}

class Root<> { 
    R<10> r;
    T<2, r.fun> t2;
    T<3, r.fun> t3;
    
    Reset {
        async t2.tt(); // task2
        async t3.tt(); // task3
        async after 1 s r.rt();
    } 
}


Unlike traditional OO languages, classes are paramtrised by arguments for the instansiation (type parameters might come later..)

In the example the object r (of type R), is instantiated with the argument <10> which matches the <int ri> of the class declaration. The formal class parameters becomes available as constant expressions (for primitive typed parameters) or method references to the class members and the initial assignments of constants. This allows the backend tool (ultimately the C) compiler to inline the constant expressions, thus parametrisation comes for free from an implementational standpoint.

Currently the compiler does not implement any type checking, but mistyping eventually turns up as compilation errors. (Type checking will be introduced during the Compiler Construction course and implemented in the compiler.)

Currently, only primitive types and methods are allowed as class parameters, extensions to Tasks, and complete object interfaces are projected.

As already mentioned, to further increase fexlibility one can think of extentions to allow for type paramters, mechanisms for interitance etc.

The instantiaion mechansim statically spans, instantiates and specialises the complete set of objects defined by the program direclty at compile time. In particular, even complex callback patterns can be expressed in a clear and succint manner as shown in exmple CEx5.coore:

// CEx5.coore
// Per Lindgren (C) 2014
//
// Mutually dependent instances can be created without dynamic installation.
// Potential deadlock may occur in cases of mutual synchrnoous calls.
class R<int my_id, void (int) other_fun> { 
    void fun(int from_id) {
        RT_printf("in fun task %d, from task %d\n", my_id, from_id);
    } 
    
    Task t () {
        RT_printf("task %d\n", my_id);
        RT_sleep(1);
        other_fun(my_id);
    }    
}

class Root<> { 
    R<1, r2.fun> r1;
    R<2, r1.fun> r2;
    
    Reset {
        async r1.t(); // task1
        async r2.t(); // task2
    } 
}


The object instance r1 refers to r2.fun, while r2 refers to r1.fun. After reset the two task instances (assyncrounous massages) r1.t and r2.t() are released and will run in a concurrent. Under bare metal execution the use of SRP based scheduling gurantee deadlock free schdeuling, however the thread based run-time system will be exposed to potential deadlock (as in this examples).

However, contrary to typical thread based programming APIs and libraries our language based approach allows the compiler (rtfm-core) to detect and report the the issue, and even give visual feedback on the task/resource pattern that is causing the problem.

In addition to the already mentioned opprtunities for improvements and extensions, language support for dynamic object instantiation is on the list. This will allow for systems with dynamic and mixed static/dynamic behaviour to be expressed in the same languge, and offer opportunities to reasoning about
robustness, etc. form a language perspective.

Word from the author.

The -cOOre language resembles the Concurrent Reactive Object (CRO) model that underpins the Timber and TinyTimber languages. Whereas, Timber is a fully fledged language (with a functional expression layer), static analysis of Timber programs has shown tricky (in fact no anaylyses whatsover has been
implemented as of yet). Still we found an apatite for the CRO mdoel as a means to specify and implement concurrent systems. From a practical perspective we implemented a C-API (to a CRO based run-time system) in terms of macros for object instantiation and syncronous/asyncronous communication.

In this context RTFM paves a middle ground in between the Timber language (as found to be too generic generic to reason on in practise), and TinyTimber C-API which clearly is too low-level and semanitaclly relaxed to argue on.

The design decisions taken aims at providing executables on par or better than hand written code. In fact, we challange anyone to write concurrent code by hand in any langue, under any run-time or kernel, and we are prepared to trade punches and goto clich without fear of being knocked- or tapped- out!

/Per Lindgren, founder of RTFM-lang 2014.

Last edited Aug 31, 2014 at 10:50 PM by RTFMPerLindgren, version 3