Pages

Sunday, December 23, 2018

Mel Global Variables

Welcome » NERWous C » Mel
  1. Global Variable Scope
  2. Global Variable Usage
    1. Semantic Sharing
    2. Code Sharing
    3. Name Discovery
    4. No Global Variables
  3. Global Variable Creation
    1. Multiple Global Variables
    2. No Exception Handling
    3. No Extern Statement
  4. Global Variable Extern Statement
    1. Extern Statement Signature
    2. Extern Statement Translated
    3. Extern Statement Chain
    4. Extern Statement Short Passing
    5. Extern Statement Multiple Values
    6. Extern Statement Macro
  5. Global Variable Closure


Global Variable Scope

In the Mel Basic chapter, we have touched on the topic of mel scope. Let's revisit the example used:
<mel> int GlobalStore;    /* global variable */
main () {
     <mel> int localStore;     /* local variable */
     <!> Producer (localStore);
     <!> Consumer ();
}
void Producer (<mel> int store) {
    <extern> GlobalStore;
    <?>GlobalStore = Produce();   /* OK - GlobalStore is global */
    <?>store = Produce();         /* OK - store is local */
    <?>localStore = Produce();    /* ERROR - localStore is not in scope */
}
void Consumer () {
    Consume (<?>GlobalStore);     /* ERROR - GlobalStore is not in scope */
    Consume (<?>store);           /* ERROR - store is not defined */
    Consume (<?>localStore);      /* ERROR - localStore is not in scope */
}
Mel variables, like GlobalStore and localStore above, appear in NERWous C programs based on their scope rules: either local or global. The enforcement of the scope rules is during compile time. The programmer has to resolve the ERRORs flagged in the above program before it can be compiled for execution.

We have seen local mel variables in use extensively in the previous chapter on Mel Operations. In this chapter, we focus on mel variables as global variables.

Note that the remote mel elements are always "global", in the sense they can be accessed at run-time anywhere in a NERWous C program. The scope rules apply only at compile time and only to the mel variables, and not the mel elements. When mel variables have local scope, they appear only inside a function or block of code. When they have global scope, they can appear anywhere in the program, but only with the help of the <extern> statement. Incorrect appearances will trigger errors during compilation.

The CORE properties of a mel variable allow a task to reach out to a mel element. For local mel variables, the CORE properties are passed by value from the parent task to the children tasks via function arguments. For a global mel variable, the CORE properties can also be passed from the parent task to the children tasks, but only if the parent task knows about this particular global mel variable. If it does not, the children tasks have to query the CHAOS runtime for the CORE properties to initialize their own global mel variables to reach out to the mel elements. In NERWous C, there is a single mel element that resides somewhere on the NERW network, but there is no single global mel variable. Each task maintains its own global mel variable in its own individual context.


Global Variable Usage

We can see the following uses for global mel variables:
  1. Semantic Sharing
  2. Code Sharing
  3. Name Discovery
Semantic Sharing

In the example above, although both the GlobalStore and localStore mel elements are accessible by any task, their meanings are different. The GlobalStore mel element is meant to be a shared resource that is common to all tasks. On the other hand, the localStore mel element is controlled regionally by the main task. Only tasks that main wants them to work on localStore, will know about localStore that main explicitly passes as argument.

Code Sharing

Let's write a new Producer/Consumer example using only the global mel variable GlobalStore:
<mel> int GlobalStore;    /* global variable */
main () {
    <!> Producer ();
    <!> Consumer ();
    printReport ();
}
void Producer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore = Produce() );
    printReport ();
}
void Consumer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore )
        Consume ( GlobalStore<value> );
    printReport ();
}
void printReport () {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        GlobalStore<status>, GlobalStore<condition>);
}
The function printReport is a serial function. It is shared by all three tasks in the program (main, Producer and Consumer) to print out the status and condition of the global mel variable GlobalStore.

When invoked from the main task, printReport makes use of the global mel variable GlobalStore that is resident in the main context. This global mel variable is different from the global mel variables GlobalStore that are used by printReport invoked from the Producer and Consumer tasks. As said previously, each task maintains its own global mel variables in its own context. By using mel variables with the global scope, they do not need to explicitly pass them around to subsequent shared functions, giving the code a "cleaner" look.

Name Discovery

All mel variables, declared with either global or local scope, are reachable by any task in the NERWous C program, but only if the tasks know about them. For local mel variables, this knowledge comes from their explicit passing from parent tasks to children tasks as functional arguments. The global mel variables are also passed in a similar way, but implicitly via the <extern> statement. However, global mel variables have a feature that local mel variables does not, which is name discovery.

The CHAOS runtime environment keeps track of mel elements with global scope: their names and their contact information (such as the CORE properties). This allows tasks that do not get the mel element information passed to them from the parent task to ask CHAOS for the contact information so they can reach out to the mel element by themselves. This is done via the on attribute of the <snapshot> operation. We will see this use in the Extern Statement Translated section.

No Global Variables

Some programmers do not like the global-variable "cleaner" look. As a matter of style, they prefer not to deal with global variables at all, be it regular C variables or NERWous C mel variables. The above Producer/Consumer example can be rewritten by replacing global mel variables with local mel variables:
main () {
    <mel> int localStore;    /* local variable */
    <!> Producer (localStore);
    <!> Consumer (localStore);
    printReport (localStore);
}
void Producer (<mel> int store) {
    while ( <?>store = Produce() );
    printReport (store);
}
void Consumer (<mel> int store) {
    while ( <?>store )
        Consume ( store<value> );
    printReport (store);
}
void printReport (<mel> int store) {
    printf ("[%s] status [%d], condition [%d]\n",
        store<name>, store<status>, store<condition>);
}
Compared with the earlier global-variable version, the local-variable version looks a little more verbose with all the local mel variables being passed around. Interestingly, the local-variable version can be used to explain in detail the behavior of a global mel variable from creation to closure. As a matter of fact, a NERWous C compilation suite may support global mel variables by translating them into local mel variables that are passed around tasks behind the scene.


Global Variable Creation

When a local mel variable is created, it is under the context of the task that runs the mel variable declaration statement. What is the task context for a global mel variable? Let's modify our Producer/Consumer example again:
<mel> int GlobalStore;    /* global variable */
main () {
    if ( GlobalStore<error> ) {
        printf ("[GlobalStore] creation failed on error [%d] due to [%s]\n",
            GlobalStore<error>, GlobalStore<why>);
        exit (911);
    }
    <!> Producer ();
    <!> Consumer ();
}
void Producer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore = Produce() );
}
void Consumer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore )
        Consume ( GlobalStore<value> );
}
Like a local mel variable, a global mel variable declaration:
<mel> int GlobalStore;
is not only a compile-time statement but also a run-time operation to allocate shared resource to set up a mel element at a cel location. (In the above example, since the at attribute is not specified, the CHAOS runtime will allocate the mel element at a default cel that hosts generic shared resources.) This remote mel element is then represented for program use by the eponymous mel variable GlobalStore.

With the global mel variable GlobalStore defined outside of any function, its resident context is of a hidden startup task. This is the one that the CHAOS runtime uses to invoke the main task. It also passes to the main task any global mel variables that it creates. The properties of these global mel variables have the same initial property values for newly created local mel variables.

The hidden startup task is not meant to run a programmer's custom code. Therefore, if a programmer needs to check the result of a global mel creation, especially over an unstable runtime environment such as a wide area network (WAN) environment, this check has to be done in an explicit task. The main task is logically the closest one to the global mel creations, so it is it that usually does the checking, as in the example above. However any task that makes use of a global mel variable, can also does its own checking before use.

Multiple Global Variables

Multiple global mel variables can be declared for compile-time scope and run-time creation:
<mel> int GlobalStore1, GlobalStore2[5];
main () {
    if ( GlobalStore1<error> ) {
        printf ("[GlobalStore1] creation failed on error [%d] due to [%s]\n",
            GlobalStore1<error>, GlobalStore1<why>);
        exit (911);
    }
    if ( GlobalStore2<error> ){
         printf ("[GlobalStore2] creation failed on error [%d] due to [%s]\n",
            GlobalStore2<error>, GlobalStore2<why>);
         exit (911+1);
    }
}

No Exception Handling

The result of a local mel variable creation can be checked against the <error> property or caught via a mel exception. However due to the global mel creation being outside of any code block, the exception method is not available for global mel variables.

In the code snippet below, we can use the exception handling for the local mel creation but not for the global one:
<mel> int GlobalStore;
main () {
    if ( GlobalStore<error> ) {
        printf ("[GlobalStore] global creation failed on error [%d] due to [%s]\n",
            GlobalStore<error>, GlobalStore<why>);
        exit (911);
    }
    try { <mel> int localStore;
    } catch ( localStore<...> ) {
        printf ("[localStore] local creation failed on error [%d] due to [%s]\n",
            localStore<error>, localStore<why>);
        exit (911+2);
    }
}
For the global mel variable, we can only use the <error> property checking.

No Extern Statement

As said above, global mel variables are created under the context of the hidden startup task and then "passed naturally" to the main task. There is no need for an <extern> statement in the main task to receive all the declared global mel variables. This is not true when main needs to "pass" the global mel variables to its children tasks, as we will see next.


Global Variable Extern Statement

Except for the main task which receives the global mel variables from the hidden startup task, other tasks that need to use a global mel variable, have to explicitly invoke an <extern> statement on that global mel variable. Unlike the C language extern statement which is solely a compile-time statement to resolve scope, the NERWous C <extern> statement is also a run-time statement. It creates in the context of the task that runs that statement, a new mel variable that points to the same remote mel element as any other mel variable of the same name that appears in the context of any other task in that particular NERWous C program.

Let's modify our Producer/Consumer example to illustrate the <extern> use:
<mel> int GlobalStore;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore = Produce() );
    printReport ();
}
void DuoConsumers () {
    <!> Consumer();
    <!> Consumer();
}
void Consumer () {
    <extern> GlobalStore;
    while ( <?>GlobalStore )
        Consume ( GlobalStore<value> );
    printReport ();
}
void printReport () {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        GlobalStore<status>, GlobalStore<condition>);
}
As said above, the <extern> statement in Producer creates a new GlobalStore mel variable in that task's context. How does that mel variable get initialized?

Extern Statement Signature

The Producer task indicates its intention to access the global mel variable GlobalStore via the statement
<extern> GlobalStore;
This statement updates the signature of the task Producer. As we will see in the pel basics chapter, a signature of a task consists of:
  1. Return values
  2. Explicit arguments
  3. Extern global variables
With its signature such updated, the Producer task requires any parent task that invokes it, to pass the global mel variable that the parent task maintains in its own context. Extern global variables can be viewed as implicit arguments, to complement the explicit functional arguments. (Neither the Producer nor Consumer versions here have functional arguments, see this example for the versions that have store as the functional argument.)

The <extern> statement is both declarative and operative. During compilation time, the <extern> declaration collects the type of the mel variable (such as int), and checks for the scope rules. During run time, the <extern> operation re-creates the global mel variable for the resident task. Like the local mel variables passed as functional arguments, the global mel variables created by the <extern> operation, will be initialized by value from the parent task.

In the latest example, there are five separate GlobalStore global mel variables. The first one is created in the context of the hidden startup task. It is then passed to the main task. This second version and the first version could be the same, depending on the NERWous C compilation suite. The third version is in the context of the Producer task. The fourth and fifth versions are in the context of each of the two Consumer tasks.

All the versions of the global mel variable GlobalStore have the same CORE properties since they access the same remote mel element. The other properties may diverge depending on what interactions the individual tasks have with the remote mel element.

All tasks share the same serial function printReport. It prints out the status and condition of the global mel variable in the context of the invoking task. It is invoked four times in the above example by different tasks, and the printf outputs can all be different.

Extern Operation Translated

NERWous C seeks to process global mel variables as much the same way as local mel variables. In fact, a NERWous C compilation suite may have a translation pass to transform global mel variables into local mel variables. A hypothetical translation could be as follows:
<mel> int GlobalStore;
main (int argc, char **argv, char **envp, char **nerw) {
    <mel> int __GlobalStore = nerw[0];
    <!> __Producer (__GlobalStore);
    <!> DuoConsumers ();
    __printReport (__GlobalStore);
}
void __Producer (<mel> int __GlobalStore) {
    if ( __GlobalStore == null ) <snapshot on="GlobalStore"> __GlobalStore;
    while ( <?>__GlobalStore = Produce() );
    __printReport (__GlobalStore);
}
void DuoConsumers () {
    <!> __Consumer(null);
    <!> __Consumer(null);
}
void __Consumer (<mel> int __GlobalStore) {
    if ( __GlobalStore == null ) <snapshot on="GlobalStore"> __GlobalStore;
    while ( <?>__GlobalStore )
        Consume ( __GlobalStore<value> );
    __printReport (__GlobalStore);
}
void __printReport (<mel> int __GlobalStore) {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        __GlobalStore<status>, __GlobalStore<condition>);
}
The hypothetical translation can help explain the working of the <extern> mel operation:
  1. The CHAOS runtime allocates shared resources for the mel element GlobalStore, and creates the eponymous mel variable with global scope.
     
  2. The CHAOS runtime passes this mel variable to the main task via the hidden nerw argument. This argument is similar to the customary argc, argv or envp that a C language runtime passes to the main function.
     
  3. The mel variable __GlobalStore that is passed to the main task contains properties with the creation initial values.
     
  4. With the appearance of the <extern> statement, the task function Producer is translated to be __Producer with <mel> int __GlobalStore as an explicit argument.
     
  5. When the main task invokes __Producer, it passes by value its __GlobalStore local variable to __Producer via the functional argument conduit.
     
  6. The task __Producer re-creates the __GlobalStore local mel variable in its own context, with properties set to passing initial values.
     
  7. When the task __Producer runs, it detects that there is a __GlobalStore mel argument passed to it, disowning the check for the null value and thus avoiding the call to the <snapshot> operation.
     
  8. The hypothetical NERWous C translator also detects the use of the global variable GlobalStore in the serial function printReport. It translates it to __printReport with the local mel variable __GlobalStore passed as argument. The serial function __printReport runs in the same task context as __Producer, and thus receives __GlobalStore by reference.
     
  9. The task function DuoConsumer does not have an <extern> statement. The hypothetical NERWous C translator does not rename it, and the main task invokes it without passing its local mel variable __GlobalStore over.
     
  10. On the other hand, the task function Consumer does include the <extern> statement, and has been translated it to be __Consumer with <mel> int __GlobalStore as its functional argument.
     
  11. Since DuoConsumer does not have a __GlobalStore local mel variable in its context, it invokes its two __Consumer tasks with the null value for the function argument.
     
  12. When each __Consumer task runs, it detects that there is no __GlobalStore passed to it. The check for the null value becomes affirmative, requiring the <snapshot> operation to be invoked with the on attribute.
     
  13. The value of the on attribute is GlobalStore, the official name of the globally scoped mel element. This instructs the CHAOS runtime to scan the NERWous C program's universe for a mel variable of the same name. If one is found, the local mel variable, __GlobalStore, is initialized with the snapshot values of that mel element.

An actual NERWous C compilation suite may or may not do the above translation. Nonetheless, the above translation describes the conceptual behavior of a global mel variable in NERWous C, as a roadmap for actual implementations.

Extern Statement Chain

The behavior of a global mel variable in NERWous C is like the behavior a local mel variable that is passed from task to task. This forms an extern statement chain. When the chain is broken, like the Duoconsumer above that skips the <extern> statement, the subsequent downstream task (here it is Consumer) has to run a discovery process to find out how to connect to the remote mel element represented by the global mel variable.

This discovery process is done under-the-hood by the CHAOS runtime via the Name Discovery process. This process could be quite expensive. If this were to impact the performance of a NERWous C program, it could be better to change the signature of the DuoConsumer task function so that the extern statement chain is maintained:
void DuoConsumers () {
    <extern> GlobalStore;
    <!> Consumer();
    <!> Consumer();
}

Extern Statement Short Passing

At run time, the <extern> statement re-creates the global mel variable in the context of the resident task. By default, this global mel variable will be created in full with the <value> and associated properties copied from the parent task (via explicit passing) or from the mel element itself by the CHAOS runtime (via the Name Discovery process). In many times, this copy is wasted since the resident task only needs some basic contact information to reach out to the remote mel element.

Like the short passing for local mel variables, a global mel variable can be initialized with short passing, by using the pass attribute to the <extern> statement:
<mel> int GlobalStore;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern pass="short"> GlobalStore;
    while ( <?>GlobalStore = Produce() );
    printReport ();
}
void DuoConsumers () {
    <!> Consumer();
    <!> Consumer();
}
void Consumer () {
    <extern short> GlobalStore;
    while ( <?>GlobalStore )
        Consume (GlobalStore<value>);
    printReport ();
}
void printReport () {
    printf ("[GlobalStore] status [%d], condition [%d]\n",
        GlobalStore<status>, GlobalStore<condition>);
}
The term pass is optional, as seen in the <extern> statement for the task function Consumer.

For the Producer task, the main task is the parent task, and will pass the contents of its resident global mel variable GlobalStore over without the <value> and associated properties. The Producer task now has its own global mel variable GlobalStore re-created with the Short Passed Mel Argument Initial Property Values.

The Consumer task has its resident global mel variable GlobalStore re-created by the CHAOS runtime, also with the Short Passed Mel Argument Initial Property Values. This time, the initial values come fresh from the mel element itself, and not potentially stale from any parent task.

Extern Statement Multiple Values

Multiple global mel variables of the same data type and passing attribute can be grouped with the same <extern> statement.
<mel> int GlobalStore1, GlobalStore2, GlobalStore3;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern> GlobalStore1, GlobalStore2;
    <extern pass=short> GlobalStore3;
    while ( <?>GlobalStore1 = Produce() &&
    <?>GlobalStore2 = Produce() );
    <?>GlobalStore3 = CloseEndProduce();
    printReport ();
}
void DuoConsumers () {
    <extern short> GlobalStore1, GlobalStore2, GlobalStore3;
    <!> Consumer(GlobalStore1);
    <!> Consumer(GlobalStore2);
    CloseEndConsume (<?>GlobalStore3);
    printReport ();
}
void CloseEndConsume (lastitem) {
 Consume (lastitem);
 <close> GlobalStore1;
 <close> GlobalStore2;
}
void Consumer (<mel> int store) {
    while ( <?>store )
        Consume ( store<value> );
}
void printReport () {
    printf ("[GlobalStore1] status [%d], condition [%d]\n",
        GlobalStore1<status>, GlobalStore1<condition>);
    printf ("[GlobalStore2] status [%d], condition [%d]\n",
        GlobalStore2<status>, GlobalStore2<condition>);
    printf ("[GlobalStore3] status [%d], condition [%d]\n",
        GlobalStore3<status>, GlobalStore3<condition>);
}
On each iteration, the single Producer task Produces two items and deposits them into the global mel elements GlobalStore1 and GlobalStore2. There are two Consumer tasks: one to Consume from GlobalStore1, and the other from GlobalStore2. When Producer ends its run, it generates a last product via CloseEndProduce. This product is not consumed by a Consume task, but by the DuoConsumers task.

After forking the two Consumer children tasks, the DuoConsumers task waits for the Producer task's CloseEndProduce run, via the GlobalStore3 global variable. It the invokes the CloseEndConsume serial function, that Consumes this last product, and invokes closure on the global variables that its Consumer child tasks are waiting for. This allows the Consumer tasks to break out of their infinite while loop via the CLOSURE exception.

Getting back to the Producer task, even though it only needs short passing for all three global mel variables, for illustration purpose, the above example has it make use of the full value passing for GlobalStore1 and GlobalStore2, and only short passing for GlobalStore3.

Extern Statement Macro

To keep the Extern Statement Chain unbroken, they need to be repeated in child tasks. To alleviate typing repetitions, C language macros can be used to group same-data-type global mel variables.
#define MYGLOBALS GlobalStore11,GlobalStore2,GlobalStore3
<mel> int MYGLOBALS;
main () {
    <!> Producer ();
    <!> DuoConsumers ();
    printReport ();
}
void Producer () {
    <extern> MYGLOBALS;
    while ( <?>GlobalStore1 = Produce() &&
    <?>GlobalStore2 = Produce() );
    <?>GlobalStore3 = CloseEndProduce();
    printReport ();
}
void DuoConsumers () {
    <extern short> MYGLOBALS;
    <!> Consumer(GlobalStore1);
    <!> Consumer(GlobalStore2);
    CloseEndConsume (<?>GlobalStore3);
    printReport ();
}
For illustration purpose, the Producer task requests full value passing from the parent task, while the DuoConsumer task only requests short passing. Both tasks make use of the macro MYGLOBALS to list the needed global mel variables.


Global Variable Closure

Any task that maintains its own resident global mel variable can invoke the <close> operation to close that global mel element. The first task that invokes the <close> operation removes all the resources that the CHAOS runtime has allocated to support shared access to the global-scoped mel element. Any subsequent access to the closed mel element by this task or any other task will result in a CLOSED mel operation exception.

We have seen the use of global mel closure in the CloseEndConsume function of an earlier example. Let's discuss closure with this simpler example:
<mel> int GlobalStore;
main () {
    <!> Producer ();
    <!> Consumer ();
}
void Producer () {
    <extern> GlobalStore;
    int c;
    while ( c = Produce() )
       <?>GlobalStore = c;

    <close>GlobalStore;
}
void Consumer () {
    <extern> GlobalStore;
    try {
        while ( 1 )
            Consume(<?>GlobalStore);
     } catch ( GlobalStore<CLOSED> ) {
         printf ("Consumer ends due to GlobalStore closed by Producer");
         return;
     }
}
The Producer and Consumer tasks communicate via the global mel element GlobalStore. Each maintains its own resident global mel variable that connects to the same remote mel element. When the Producer generates a 0-valued product, it stops Produce'ing more items. To inform the Consumer task that there are no more forthcoming products, it invokes the <close> operation on GlobalStore.

The Consumer keeps Consume'ing whatever coming up at the global mel element GlobalStore, until it gets hit by a <CLOSED> exception. With its understanding of the Producer behavior, the Consumer task knows that there will be no more products for it to Consume; it thus makes a printf notification, and then ends itself via the return statement.

The main task forks the Producer and Consumer tasks, then ends. The Producer task after closing GlobalStore also ends. The Consumer task after handling the <CLOSED> exception, also ends. With all the tasks having ended, the CHAOS runtime terminates the NERWous C program.


Previous Next Top

No comments:

Post a Comment