This project is read-only.

A candidate cOOre module system

The binding of -core code to -cOOre objects is be very useful to the integration with legacy code and hosting operating system services (for example socket interfaces under Windows/POSIX, as well as 3rd party device drivers on bare metal). In the following we sketch an extension to -cOOre/-core that provides:
  • synchronous two-way communication between -core and -cOOre
  • asynchronous two-way communication between -core and -cOOre
  • access of -cOOre state variables from -core code
  • local resources and modules in -core
  • zero overhead during run-time (complete specialisation as usual)

In this way -cOOre is C-complete, i.e. any application possible to code in C, can be written/ported to -cOOre. For the integration, the required syntactic changes are minimal (is possible by mere extensions to existing syntax in fact).

Notice however, that the module system is still undertakes a single top level name-space. This is good enough as a proof of concept, but namespaces would largely help program development. (Namespaces are projected future work, ...)

In the following we will discuss more in detail a suggested (candidate) syntax, show examples, and discuss implementation details.

Let us start by the simplest possible integration of -core into -cOOre, the file ExtTest1.coore:

ExtTest1.core

// ExtTest1.coore
// Per Lindgren (C) 2014
//
// Simple example of external -core binding

class Root() extern "ExtRoot.core" {
  Reset {
    f(10);
  }
}

and the accompanying ExtRoot.core
// ExtRoot1.core
// Per Lindgren (C) 2014
//

Func void f_local(int v) {
  #> printf("core Func f_local: arg v%d \n", v); <#
}


As simple as 1,2,3! In -cOOre we introduce the extern keyword followed by a file name (in this case "ExtRoot.core"), which points to the file to be included by the -core compiler.

In the corresponding -core file we simply give the implementation of the Func f with the corresponding signature (a single argument in this case). If we expect it to called ONLY from the parenting object, it is enough to give :
Func void f_local(int v) {...}


Recall, that the local definitions were introduced a while back as a means to run under the claim of the parenting object. Without distinguishing local from ordinary function we would run into the situation where we try to re-claim a lock, that we have already taken (causing a sort of deadlock). We chose to allow re-claiming ONLY the case is stems from a direct synchronous call from within the same object (and hence discarding re-claims occurring in sync chains.)

This leads us into the first discussion on how this can be be achieved by the -cOOre/-core compilers.

Looking deeper at the problem, both -cOOre and -core perform specialisation, (-cOOre on basis of object instances, while -core on basis of task and function instances.)

For this example, the -cOOre compiler will generate an instance of root (of the class definition Root). Any tasks and functions will be pre-pended with root_, e.g., the function f(...) in the root instance is assumed the qualified name root_f(...) and the local function root_f_local(...) accordingly. (Notice the subtle change of post pending local instead of prepending it, the reason will soon be revealed...)

To get this working, we need the collaboration of the -core compiler. We have given the definition for f_local(...), but for the correct composition with the caller assuming root_f_local(...) we need to instruct -core to specialise the inclusion of "ExtRoot.core".

Already today the -core language accept
include [file.core]

as part of the header (before task,func and top level C code).

We extend -core to (additionally) accept
include [file.core] as [path]

and let the -cOOre compiler emit such an include statement for each class instance that carry an extern keyword.

In the running example, this amounts to rendering an output file headed by:
include "ExtRoot1.core" as "root"


To this, the expected behaviour of the -core compiler is to specialise
Func void f_local(int v) {...}

into
Func void root_f_local(int v) {...}

Looking back to the discussion above, this is the reason to the subtle change of local position. From an implementation perspective it would be more difficult to specialise
... local_f...

into
... local_root_f...

(This would require parsing of the identifier (in this case local_f), splitting it into (local, f), detecting that is a local definition, and in such case reassemble it into local_root_f. The suggested change eliminates this problem, and specialisation always amounts to prepending the path to the identifier.)

The implementation is straightforward. Without going too deep into details, we find that the -core compiler, parses the input (.core) file and recursively includes and parses other .core files being referred to. After parsing a file (x.core), the rendered top level declarations are concatenated together to form the complete list of top level declaration. In a step in between parsing and concatenating, the specialisation can be inserted.

Let us now look at the next example, showing how state access can be achieved, ExtTest2.coore:

ExtTest2.core

// ExtTest2.coore
// Per Lindgren (C) 2014
//
// State access in -core binding

class Root() extern "ExtRoot2.core" {
  int i := 77;
  Reset {
    f(10);
    RT_printf("Root i=%d\n", i);
  }
}

and the accompanying ExtRoot2.core:
// ExtRoot2.core
// Per Lindgren (C) 2014
//

Func void f_local(int v) {
  #> printf("core Func f_local: arg v%d state %d\n", v, <#_STATE_#> i++); <#
}


The problem at hand is to allow (safe) access to the state of the parenting object. Recall, in -core, we do not do any attempt to parse and analyse the inlined C-constructs, they are merely stored and reproduced literally. However, by making state references explicit in the inlined C-code (lifting them to the -core level), we can specialise state access to the "path" (given by the parenting object).

For this specific case this render us the specialised -core construct:
Func void root_f_local(int v) {
  #> printf("core Func f_local: arg v%d state %d\n", v, root_i++); <#
}

Notice, this does not require the programmer (of the -core part) to explicitly refer the the class name, this will be automatically derived during the compilation process. (Of course, he/she/"swedish hen", needs to know the name of the state variable, but that is mandatory information.

The solutions is optimal! The is NO language (or compiler) that could possibly provide state access in any cheaper way, the state access is DIRECT, without the need to pass around state pointers, and dynamically dereference them. Overhead is, as we learn to expected for RTFM based solutions, zero!

But what about state integrity. Well so far we have only looked to local functions (executing under the claim of the parenting object).

Let us now solve the state integrity problem, ExtTest3.coore:

ExtTest3.core

// ExtTest3.coore
// Per Lindgren (C) 2014
//
// State access in -core binding

class Root() extern "ExtRoot3.core" {
  int i := 77;
  Reset {
    f(10);
    async after 1s t(i*2);
    RT_printf("Root i=%d\n", i);
  }
}

with the corresponding ExtRoot3.core
// ExtRoot3.core
// Per Lindgren (C) 2014
//

Func void f_local(int v) {
  #> printf("core Func f_local: arg v%d state %d\n", v, <#_STATE_#> i++); <#
}

Task t(int v) {
  claim {
    #> printf("core Task t: arg v%d state %d\n", v, <#_STATE_#> i++); <#
  }

Notice, that in the Task t we claim the parenting object implicitly (no resource name given). During specialisation the qualified name is derived (similarly to the _STATE_ derivation already mentioned.

And a slightly more elaborated example, ExtTest4.coore:

ExtTest4.core

{{
// ExtTest4.coore
// Per Lindgren (C) 2014
//
// State access in -core binding

class Root() extern "ExtRoot4.core" {
int i := 77;

void par_f(int v) {
RT_printf("par_f Root arg=%d\n", v);
}

Task par_t(int v) {
RT_printf("par_t Root arg=%d\n", v);
}

Reset {
f(10);
async after 1s t(i*2);
RT_printf("Root i=%d\n", i);
}
}
with the corresponding ExtRoot4.core
// ExtRoot4.core
// Per Lindgren (C) 2014
//

Func void f_local(int v) {
  #> printf("core Func f_local: arg v%d state %d\n", v, <#_STATE_#> i++); <#
  async par_t(v+1); 
}

Task t(int v) {
  claim {
    #> printf("core Task t: arg v%d state %d\n", v, <#_STATE_#> i++); <#
    sync par_f_local(v*2);
  }
}

In this example we show that communication goes both ways. It is possible to access the parenting functions (methods) and Tasks from the -core code. This is INSTRUMENTAL, this allows us to make callbacks into the -cOOre object model from the environment. An example thereof is the Websocket connection. More on that later...

For now we have studied external bindings exclusively for the Root object (and its root instance). The final example shows how we deal with objects and bindings in general, ExtTest5.coore:

ExtTest5.core

// ExtTest5.coore
// Per Lindgren (C) 2014
//
// State access in -core binding

class O(int cv) extern "ExtO5.core" {
  int ci := cv + 5;
}

class Root() {
  O(1) o1; 
  O(2) o2; 
  
  Reset {
    o1.f(54);
    o2.f(99);
  }
}

and correspondingly, ExtO5.core
// ExtO5.core
// Per Lindgren (C) 2014
//

Func void f(int v) {
  claim {
      #> printf("core Func O.f: arg v=%d, class arg cv=%d, state ci=%d\n", v, <#_STATE_#>cv, <#_STATE_#>ci); <#
  } 
}


This example showcases the ability to create multiple instances of the same generic external bindings. As previously discussed, the anonymous claim and _STATE_ will be specialised w.r.t to the corresponding resource/state. In this context, the class arguments is accessed as a _STATE_ reference. In the generated code class arguments can be made non-volatile (declared as constants). This allows the backend compiler (e.g., gcc) to treat it as constants to the assembly generation. (I.e., no ram-memory will be allocated, and the constants will be directly available for further backend optimisation).

---

Word from the author.

This is a first proposal, and implemented in as a prototype in the internal development branch. It has not been tested beyond the examples given, and errors may still to be found.

Nevertheless, the proposal suggest that -core bindings are indeed feasible, and that with only small extensions to the current compiler. Moreover, the key advantages of full specialisation is completely maintained. To this end, still -core + -cOOre offers machine generated code with efficiency on par (or better than) with hand written C-code.

In this presentation, we have excluded type checking. The observant reader may already have asked himself, how was the ExtRoot/ExtO functions and Tasks made visible.

The answer is simple, it was not! However, the without the type-checker enabled, -cOOre blindly generates -core code assuming that the corresponding functions/Tasks are available (with the correct types), and relies on that the backend -core and C-compiler and linker will spot potential violations thereof.

Although functional, this approach is not to our full satisfaction, and also to this end we have suggested solution.

// ExtTest5b.coore
// Per Lindgren (C) 2014
//
// State access in -core binding

class O(int cv) extern "ExtO5.core" {
  int ci := cv + 5;
  extern void f(int);
}

class Root() {
  O(1) o1; 
  O(2) o2; 
  
  Reset {
    o1.f(54);
    o2.f(99);
  }
}

giving the following AST:
AST dump:
class O (int cv) extern "ExtO5.core" {
	int ci := (cv+5);
	// extern Func void f (int)

}

class Root ()  {
	O <1> o1;
	O <2> o2;
	Reset
	{
		sync o1.f (54) ;
		sync o2.f (99) ;
	}

}

The introduction of the type declaration (in this case of a Func) serves the mere purpose to include tasks and functions to the class interface. This information can later be utilised for the purpose of type checking.

(For now we have followed the classical OO approach (Java style) to automatically infer interfaces from object declaration, but other approaches could be considered.)

Though the proposed module system and bindings via -core to C is simple, we are already at the point where we can claim -cOOre to be C-complete. I.e., allow any program implementable in C to be written/ported to -cOOre + -core.

Per Lindgren, founder of RTFM, October 2014.















Last edited Oct 5, 2014 at 12:53 AM by RTFMPerLindgren, version 6