Table of Contents Previous Chapter 36 The Master Library

36 The Master Library

The Symbol Table

The symbol table is used for storing information mainly about the static properties of the SDL system, such as the block structure, connections of channels and the valid input signal set for processes. Some dynamic properties are also placed in the symbol table; for example the list of all active process instances of a process instance set. This is part of the node representing the process instance set.

The nodes in the symbol table are structs with components initialized in the declaration. During the initialization of the application, in the yInit function in generated code, a tree is built up from these nodes.

Symbol Table Tree Structure

The symbol table is created in two steps:

  1. First, symbol table nodes are declared as structs with components initialized in the declaration (in generated code).
  2. Then, the yInit function (in generated code) updates some components in the nodes and builds a tree from the nodes. This operation is not needed in an application!
The following names can be used to refer to some of the nodes that are always present. These names are defined in scttypes.h.

xSymbolTableRoot;
xSystemId;
xEnvId;
xSrtN_SDL_Boolean; 
xSrtN_SDL_Character;
xSrtN_SDL_Charstring;
xSrtN_SDL_Duration;
xSrtN_SDL_Integer;
xSrtN_SDL_Natural;
xSrtN_SDL_PId;
xSrtN_SDL_Real;
xSrtN_SDL_Time;
xSymbolTableRoot is the root node in the symbol table tree. Below this node the system node is inserted (referred to by xSystemId). After the system node, there is a node representing the environment of the system (xEnvId). Then there is one node for each package referenced from the SDL system. This is true also for the package predefined containing the predefined data types. The nodes for the predefined data types, that are sons to the node for the package predefined, can be directly referenced by the names xSrtN_SDL_xxx, according to the list above.

Nodes in the symbol table are placed in the tree exactly according its place of declaration in SDL. A node that represent an item declared in a block is placed as a child to that block node, and so on. The hierarchy in the symbol table tree will directly reflect the block structure and declarations within the blocks and processes.

A small example can be found in Figure 560 on page 2194. The following node types will be present in the tree:

---------------------------------------------------------------------
Node Type          Description                                         
---------------------------------------------------------------------
xSystemEC          Represent the system or the system instance.        
xSystemTypeEC      Represents a system type.                           
xPackageEC         Represents a package.                               
xBlockEC           Represent blocks and block instances.               
xBlockTypeEC       Represents a block type.                            
xBlockSubstEC      Represents a block substructure and can be          
                   found as a child of a block node.                   
xProcessEC         Represent processes and process instances.          
                   The environment process node is placed after        
                   the system node and is used to represent the        
                   environment of the system.                          
xProcessTypeEC     Represents a process type.                          
xServiceEC         Represents a service or service instance.           
xServiceTypeEC     Represents a service type.                          
xProcedureEC       Represents a procedure.                             
xOperatorEC        Represents an operator diagram, i.e. an ADT         
                   operator with an implementation in SDL.             
xSignalEC          Represents a signal or timer type.                  
xTimerEC                                                               
xRPCSignalEC       Represents the implicit signals (pCALL,             
                   pREPLY) used to implement RPCs.                     
xSignalParEC       There will be one signal parameter node, as a       
                   child to a signal, timer, and RPC signal node,      
                   for each signal or timer parameter.                 
xStartUpSignalEC   Represents a start-up signal, that is, the sig      
                   nal sent to a newly created process contain         
                   ing the actual FPAR parameters. An                  
                   xStartUpSignalEC node is always placed              
                   directly after the node for its process.            
xSortEC
Represents a newtype or a syntype. xSyntypeEC Struct A sort node representing a struct has one Component Node
struct component node as child for each (xVariableEC) struct component in the sort definition. xLiteralEC A sort node similar to an enum type has one literal node as child for each literal in literal list. xStateEC Represents a state and can be found as a child to process and procedure nodes. xVariableEC
Represents a variable (DCL) or a formal xFormalParEC parameter (FPAR) and can be found as chil dren of process and procedure nodes xChannelEC
Represents a channel, a signal route, or a xSignalRouteEC gate. xGate xRemoteVarEC Represents a remote variable definition. xRemotePrdEC Represents a remote procedure definition. ---------------------------------------------------------------------
The nodes (the struct variables) will in generated code be given names according to the following table:

ySysR_SystemName
        (system, system type, system instance)
yPacR_PackageName
yBloR_BlockName
        (block, block type, block instance)
yBSuR_SubstructureName
yPrsR_ProcessName
        (process, process type, process instance)
yPrdR_ProcedureName  (procedure, operator)
ySigR_SignalName
        (signal, timer, startup signal, RPC signal)
yChaR_ChannelName    (channel, signal route, gate)
yStaR_StateName
ySrtR_NewtypeName    (newtype, syntype)
yLitR_LiteralName
yVarR_VariableName
       (variable, formal parameter, signal
        parameter, struct component)
yReVR_RemoteVariable
yRePR_RemoteProcedure
In most cases it is of interest to refer to a symbol table node via a pointer. By taking the address of a variable according to the table above, i.e.

& yPrsR_Process1
such a reference is obtained. For backward compatibility, macros according to the following example is also generated for several of the entity classes:

#define yPrsN_ProcessName  (&yPrsR_ProcessName)

Types Representing the Symbol Table Nodes

The following type definitions, from the file scttypes.h, are used in connection with the symbol table:

typedef enum {
  xExportEC,
  xRemoteVarEC,
  xRemotePrdEC,
  xSignalrouteEC,
  xStateEC,
  xTimerEC,
  xFormalParEC,
  xLiteralEC,
  xVariableEC,
  xViewEC,
  xBlocksubstEC,
  xChannelsubstEC,
  xPackageEC,
  xProcedureEC,
  xOperatorEC,
  xProcessEC,
  xProcessTypeEC,
  xGateEC,
  xSignalEC,
  xSignalParEC,
  xStartUpSignalEC,
  xRPCSignalEC,
  xSortEC,
  xSyntypeEC,
  xSystemEC,
  xSystemTypeEC,
  xBlockEC,
  xBlockTypeEC,
  xChannelEC,
  xServiceEC,
  xServiceTypeEC,
  xMonitorCommandEC
}   xEntityClassType;
typedef enum {
  xPredef, xUserdef, xEnum,
  xStruct, xArray, xGArray, xString,
  xPowerSet, xGPowerSet, xInherits, xSyntype,
  xUnion
}   xTypeOfSort;
typedef char  *xNameType;
typedef struct xIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
}  xIdRec;
                                  /*BLOCKSUBSTRUCTURE*/
typedef struct xBlockSubstIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
}  xBlockSubstIdRec;
                                  /*LITERAL*/
typedef struct xLiteralIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
}  xLiteralIdRec;
                                  /*PACKAGE*/
typedef struct xPackageIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
}  xPackageIdRec;
                                  /*SYSTEM*/
typedef struct xSystemIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xIdNode         * Contents;
   xPrdIdNode      * VirtPrdList;
   xSystemIdNode     Super;
#ifdef XTRACE
   int               Trace_Default;
#endif
#ifdef XGRTRACE
   int               GRTrace;
#endif
#ifdef XMSCE
   int               MSCETrace;
#endif
}  xSystemIdRec;
                                  /*CHANNEL,SIGNALROUTE*/
#ifndef XOPTCHAN
typedef struct xChannelIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xSignalIdNode    *SignalSet; /*Array*/
   xIdNode          *ToId;      /*Array*/
   xChannelIdNode    Reverse;
}  xChannelIdRec;   /* And xSignalRouteEC.*/
#endif
                                  /*BLOCK*/
typedef struct xBlockIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xBlockIdNode      Super;
   xIdNode         * Contents;
   xPrdIdNode      * VirtPrdList;
   xViewListRec    * ViewList;
   int               NumberOfInst;
#ifdef XTRACE
   int               Trace_Default;
#endif
#ifdef XGRTRACE
   int               GRTrace;
#endif
#ifdef XMSCE
   int               MSCETrace;
   int               GlobalInstanceId;
#endif
}  xBlockIdRec;
                                  /*PROCESS*/
typedef struct xPrsIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xStateIdNode     *StateList;
   xSignalIdNode    *SignalSet;
#ifndef XNOUSEOFSERVICE
   xIdNode          *Contents;
#endif
#ifndef XOPTCHAN
   xIdNode          *ToId; /*Array*/
#endif
   int               MaxNoOfInst;
#ifdef XNRINST
   int               NextNr;
   int               NoOfStaticInst;
#endif
   xPrsNode         *ActivePrsList;
   xptrint           VarSize;
#if defined(XPRSPRIO) || defined(XSIGPRSPRIO) || \
    defined(XPRSSIGPRIO)
   int               Prio;
#endif
   xPrsNode         *AvailPrsList;
#ifdef XTRACE
   int               Trace_Default;
#endif
#ifdef XGRTRACE
   int               GRTrace;
#endif
#ifdef XBREAKBEFORE
#ifndef ULTRIXCC
   char             *(*GRrefFunc)
                      XPP((int, xSymbolType *));
#else
   char             *(*GRrefFunc) ();
#endif
   int               MaxSymbolNumber;
   int               SignalSetLength;
#endif
#ifdef XMSCE
   int               MSCETrace;
#endif
#ifdef XCOVERAGE
   long int         *CoverageArray;
   long int          NoOfStartTransitions;
   long int          MaxQueueLength;
#endif
   void              (*PAD_Function) XPP((xPrsNode));
   xPrsIdNode        Super;
   xPrdIdNode      * VirtPrdList;
   xBlockIdNode      InBlockInst;
#ifdef XBREAKBEFORE
   char            * RefToDefinition;
#endif
}  xPrsIdRec;
#ifndef XNOUSEOFSERVICE
                                  /*SERVICE*/
typedef struct xSrvIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xStateIdNode     *StateList;
   xSignalIdNode    *SignalSet;
#ifndef XOPTCHAN
   xIdNode          *ToId;
#endif
   xptrint           VarSize;
#ifdef XBREAKBEFORE
#ifndef ULTRIXCC
   char   *(*GRrefFunc) XPP((int, xSymbolType *));
#else
   char             *(*GRrefFunc) ();
#endif
   int               MaxSymbolNumber;
   int               SignalSetLength;
#endif
#ifdef XCOVERAGE
   long int         *CoverageArray;
   long int          NoOfStartTransitions;
#endif
   xSrvNode         *AvailSrvList;
   void          (*PAD_Function) XPP((xPrsNode));
   xSrvIdNode        Super;
   xPrdIdNode      * VirtPrdList;
}  xSrvIdRec;
#endif
                                  /*PROCEDURE*/
typedef struct xPrdIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xStateIdNode     *StateList;
   xSignalIdNode    *SignalSet; 
   xbool             (*Assoc_Function)
                           XPP((xPrsNode));
   xptrint           VarSize;
   xPrdNode         *AvailPrdList;
#ifdef XBREAKBEFORE
#ifndef ULTRIXCC
   char             *(*GRrefFunc)
                        XPP((int, xSymbolType *));
#else
   char             *(*GRrefFunc) ();
#endif
   int               MaxSymbolNumber;
   int               SignalSetLength;
#endif
#ifdef XCOVERAGE
   long int         *CoverageArray;
#endif
   xPrdIdNode        Super;
   xPrdIdNode      * VirtPrdList;
}  xPrdIdRec;
typedef struct xRemotePrdIdStruct {
   xEntityClassType    EC;
#ifdef XSYMBTLINK
   xIdNode             First;
   xIdNode             Suc;
#endif
   xIdNode             Parent;
#ifdef XIDNAMES
   xNameType           Name;
#endif
   xRemotePrdListNode  RemoteList;   
}  xRemotePrdIdRec;
                                /* SIGNAL, TIMER */
typedef struct xSignalIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xptrint           VarSize;
   xSignalNode      *AvailSignalList;
#ifndef ULTRIXCC
   xbool             (*Equal_Timer)
                           XPP((void *, void *));
#else
   xbool             (*Equal_Timer) ();
#endif
#ifdef XFREESIGNALFUNCS
#ifndef ULTRIXCC
   void              (*Free_Signal) XPP((void *));
#else
   void              (*Free_Signal) ();
#endif
#endif
#ifdef XBREAKBEFORE
   char               *RefToDefinition;
#endif
}  xSignalIdRec;   /* And xTimerEC, xStartUpSignalEC,
                    and xRPCSignalEC.*/
                                  /*STATE*/
typedef struct xStateIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   int               StateNumber;
   xInputAction     *SignalHandlArray;
   int              *InputRef;
#ifndef ULTRIXCC
   xInputAction      (*EnablCond_Function)
                          XPP((XSIGTYPE, void *));
   void              (*ContSig_Function)
           XPP((void *, int *, xIdNode *, int *));
#else
   xInputAction      (*EnablCond_Function) ();
   void              (*ContSig_Function) ();
#endif
   int               StateProperties;
#ifdef XCOVERAGE
   long int         *CoverageArray;
#endif
   xStateIdNode      Super;
#ifdef XBREAKBEFORE
   char             *RefToDefinition;
#endif
}  xStateIdRec;
                                  /*SORT*/
typedef struct xSortIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
#ifdef XFREEFUNCS
   void              (*Free_Function) XPP((void **));
#endif
#ifdef XREADANDWRITEF
   int               (*Read_Function) XPP((void *));
   char             *(*Write_Function) XPP((void *));
#endif
#ifdef XTESTF
   xbool             (*Test_Function) XPP((void *));
#endif
   xptrint           SortSize;
   xTypeOfSort       SortType;
   xSortIdNode       CompOrFatherSort;
   xSortIdNode       IndexSort;
   long int          LowestValue;
   long int          HighestValue;
}  xSortIdRec;
                                  /*VARIABLE,...*/
typedef struct xVarIdStruct {
   xEntityClassType  EC;
#ifdef XSYMBTLINK
   xIdNode           First;
   xIdNode           Suc;
#endif
   xIdNode           Parent;
#ifdef XIDNAMES
   xNameType         Name;
#endif
   xSortIdNode       SortNode;
   xptrint           Offset;
   xptrint           Offset2;
   xbool             IsAddress;
}  xVarIdRec;    /* And xFormalParEC and
                        xSignalParEC.*/
typedef struct xRemoteVarIdStruct {
   xEntityClassType     EC;
#ifdef XSYMBTLINK
   xIdNode              First;
   xIdNode              Suc;
#endif
   xIdNode              Parent;
#ifdef XIDNAMES
   xNameType            Name;
#endif
   xptrint              SortSize;
   xRemoteVarListNode   RemoteList;
}  xRemoteVarIdRec;
There are also pointer types defined for each of the xECIdStruct according to the following example:

typedef XCONST struct xIdStruct  *xIdNode;
The type definitions above define the contents in the symbol table nodes. Each xECIdStruct, where EC should be replaced by an appropriate string, have the first five components in common. These components are used to build the symbol table tree. To access these components, a pointer to a symbol table node can be type cast to any of the xIdECNode types. The type xIdNode is used as such general type, for example when traversing the tree.

The five components present in all xIdNode are:

Next there are components depending on what entity class that is to be represented. Below we discuss the non-common elements in the other xECIdStruct.

System, System Type

Channel, Signal route, Gate

For channels, signal routes, and gates there are always two consecutive xChannelIdNodes in the symbol table, representing the two possible directions for a channel, signal route, or gate. The components are:

Block, Block Type, Block Instance

Process, Process Type, Process Instance

Service, Service Type, Service Instance

Procedure

Remote Procedure

Signal, Timer, StartUpSignal, and RPC Signals

State

Sort and Syntype

SortType is xArray, xGArray

SortType is xString

SortType is xPowerSet, xGPowerSet

SortType is xInherits

SortType is xSyntype

Variable, FormalPar, SignalPar, and Struct Components

Remote Variable

The SDL Model

Signals and Timers

Data Structure Representing Signals and Timers

A signal is represented by a struct type. The xSignalRec struct, defined in scttypes.h, is a struct containing general information about a signal except from the signal parameters. In scttypes.h the following information about signals can be found:

#ifdef XMSCE
#define GLOBALINSTID  int GlobalInstanceId;
#else
#define GLOBALINSTID
#endif
#ifdef XENV_CONFORM_2_3
#define XSIGNAL_VARP  void * VarP;
#else
#define XSIGNAL_VARP
#endif
#define SIGNAL_VARS \
   xSignalNode   Pre; \
   xSignalNode   Suc; \
   int           Prio; \
   SDL_PId       Receiver; \
   SDL_PId       Sender; \
   xSignalIdNode NameNode; \
   XSIGNAL_VARP \
   GLOBALINSTID
typedef struct xSignalStruct  *xSignalNode;
typedef struct xSignalStruct {
   SIGNAL_VARS
}  xSignalRec;
The xSignalNode type is thus a pointer type which is used to refer to allocated data areas of type xSignalRec. The components in the xSignalRec struct are used as follows:

A signal without parameters are represented by a xSignalStruct, while for signals with parameters a struct type named yPDef_SignalName and a pointer type referencing this struct type (yPDP_SignalName) are defined in generated code. The struct type will start with the SIGNAL_VARS macro and then have one component for each signal parameter, in the same order as the signal parameters are defined. The components will be named Param1, Param2, and so on.

Example 136   
typedef struct {
    SIGNAL_VARS
    SDL_Integer  Param1;
    SDL_Boolean  Param2;
} yPDef_sig;
typedef yPDef_sig  *yPDP_sig;
These types would represent a signal sig(Integer, Boolean).

  
As all signals starts with the components defined in SIGNAL_VARS it is possible to type cast a pointer to a signal, to the xSignalNode type, if only the components in SIGNAL_VARS is to be accessed.

Allocation of Data Areas for Signals

In sctos.c there are two functions, xGetSignal and xReleaseSignal, where data areas for signal are handled:

xSignalNode xGetSignal(
  xSignalIdNode  SType,
  SDL_PId        Receiver,
  SDL_PId        Sender )
void xReleaseSignal( xSignalNode  *S )
xGetSignal takes a reference to the SignalIdNode identifying the signal type and two PId values (sending and receiving process instance) and returns a signal instance. xGetSignal first looks in the avail list for the signal type (the component AvailSignalList in the SignalIdNode for the signal type) and reuses any available signal there. Only if the avail list is empty new memory is allocated. The component VarSize in the SignalIdNode for the signal type provides the size information needed to correctly allocate the yPDef_SignalName even though the type is unknown for the xGetSignal function.

The function xReleaseSignal takes the address of an xSignalNode pointer and returns the referenced signal to the avail list for the signal type. The xSignalNode pointer is then set to 0.

The function xGetSignal is used:

The function xReleaseSignal is used by:

Overview of Output and Input of Signals

In this subsection the signal handling operation is only outlined. More details will be given in the section treating processes. See "Output and Input of Signals" on page 2175.

Signal instances are sent using the function SDL_Output. That function takes a signal instance and inserts it into the input port of the receiving process instance.

If the receiver is not already in the ready queue (the queue containing the processes that can perform a transition, but which have not yet been scheduled to do so) and the current signal may cause an immediate transition, the process instance is inserted into the ready queue.

If the receiver is already in the ready queue or in a state where the current signal should be saved, the signal instance is just inserted into the input port.

If the signal instance can neither cause a transition nor should be saved, it is immediately discarded (the data area for the signal instance is returned to the avail list).

The input port is scanned during nextstate operations, according the rules of SDL, to find the next signal in the input port that can cause a transition. Signal instances may then be saved or discarded.

There is no specific input function, instead this behavior is distributed both in the runtime library and in the generated code. The signal instance that should cause the next transition to be executed is removed from the input port in the main loop (the scheduler), immediately before the PAD function for the current process is called. The PAD function is the function where the behavior of the process is implemented and is part of the generated code. The assignment of the signal parameters to local SDL variables is one of the first actions performed by the PAD function.

The signal instance that caused a transition is released and returned to the avail list in the nextstate or stop action that ends the current transition.

Timers and Operations on Timers

A timer with parameters is represented by a type definition, where the timer parameters are defined, in exactly the same way as for a signal definition, see "Data Structure Representing Signals and Timers" on page 2157. At runtime, all timers that are set and where the timer time has not expired, are represented by a xTimerRec struct and a signal instance:

#define TIMER_VARS \
   xSignalNode   Pre; \
   xSignalNode   Suc; \
   int           Prio; \
   SDL_PId       Receiver; \
   SDL_PId       Sender; \
   xSignalIdNode NameNode; \
   GLOBALINSTID \
   SDL_Time      TimerTime;
typedef xTimerRec  *xTimerNode;
typedef struct xTimerStruct {
   TIMER_VARS
}  xTimerRec;
The TIMER_VARS is and must be identical to the SIGNAL_VARS macro, except for the TimerTime component last in the macro. A timer with parameters have yPDef_timername and yPDP_timername types in generated code exactly as a signal (see previous section), except that SIGNAL_VARS is replaced by TIMER_VARS.

During its life-time a timer have two different appearances. First it is a timer waiting for the timer time to expire. In that phase the timer is inserted in the xTimerQueue. When the timer time expires the timer becomes a signal and is inserted in the input port of the receiver just like any other signal. Due to the identical typedefs for xSignalRec and xTimerRec, there are no problems with type casting between xTimerNode and xSignalNode types.

When a timer is treated as a signal the components in the xTimerRec are used in the same ways as for a xSignalRec. While the timer is in the timer queue, the components are used as follows:

The queue mentioned above, the timer queue for active timers is represented by the component xTimerQueue in the variable xSysD:

  xTimerNode xTimerQueue;
The variable is initialized in the function xInitKernel in sctsdl.c. xTimerQueue is initialized it refers to the queue head of the timer queue.

The queue head is an extra element in the timer queue that does not represent a timer, but is introduced as it simplifies the algorithms for queue handling. The TimerTime component in the queue head is set to a very large time value (xMaxTime).

The timer queue is thus a doubly linked list with a list head and it is sorted according to the timer times, so that the timer with lowest time is at the first position.

The xTimerRec structs are allocated and reused in the same way as signal.

From the SDL point of view, timers are handled in:

The timer output is the event when the timer time has expired and the timer signal is sent. After that, a timer signal is treated as an ordinary signal. These operations are implemented as follows:

void SDL_Set(
  SDL_Time     T,
  xSignalNode  S )
This function, which represents the Set operation, takes the timer time and a signal instance as parameters. It first uses the signal instance to make an implicit reset (see reset operation below) It then updates the TimerTime component in S and inserts S into the timer queue at the correct position.

The SDL_Set operation is used in generated code, together with xGetSignal, in much the same way as SDL_Output. First a signal instance is created (by xGetSignal), then timer parameters are assigned their values, and finally the Set operation is performed (by SDL_Set).

void SDL_Reset( xSignalNode  *TimerS )
void SDL_SimpleReset(
  xPrsNode       P,
  xSignalIdNode  TimerId )
Two functions are used to represent the SDL action reset. SDL_SimpleReset is used for timers without parameters and SDL_Reset for timers with parameters.

SDL_Reset uses the two functions xRemoveTimer and xRemoveTimerSignal to remove a timer in the timer queue and to remove a signal instance in the input port of the process. It then releases the signal instance given as parameter. This signal is only used to carry the parameter values given in the reset action.

The function SDL_SimpleReset is implemented in the same way as SDL_Reset, except that it creates its own signal instance (without parameters).

At a reset action the possibly found timer is removed from the timer queue and returned to the avail list. A found signal instance (in the input port) is removed from the input port and returned to the avail list for the current signal type.

static void SDL_OutputTimerSignal( xTimerNode  T )
The SDL_OutputTimerSignal is called from the main loop (the scheduler) when the timer time has expired for the timer first in the timer queue. The corresponding signal instance is then sent.

SDL_OutputTimerSignal takes a pointer to an xTimerRec as parameter, removes it from the timer queue and sends as an ordinary output using the function SDL_Output.

It can be checked if timer is active by using a call to the function SDL_Active. This function is used in generated code to represent the SDL operator active.

SDL_Boolean SDL_Active (
  xSignalIdNode TimerId,
  xPrsNode      P )
-----------------------------------------------------------------------
Note:                                                                    
Only timers without parameters can be tested. This is a restriction in   
the C Code Generator.                                                    
-----------------------------------------------------------------------
There is one more place where timers are handled. When a process instance performs a stop action all timers in the timer queue connected to this process instance are removed. This is performed by calling the function xRemoveTimer with the first parameter equal to 0.

Processes

Data Structure Representing Processes

A process instance is represented by two structs, an xLocalPIdRec and a struct containing both the general process data and the local variables and formal parameters of the process (yVDef_ProcessName), see also Figure 554 on page 2165. The reason for having both the xLocalPIdRec and the yVDef_ProcessName will be discussed under "Create and Stop Operations" on page 2171.

Figure 554 : Representation of a Process Instance. 
-----
(fig)  
       
-----
The corresponding type definitions, which can be found in scttypes.h, are:

#ifdef XPRSSENDER
#define XPRSSENDERCOMP   SDL_PId  Sender;
#else
#define XPRSSENDERCOMP
#endif
#ifdef XTRACE
#define XTRACEDEFAULTCOMP   int Trace_Default;
#else
#define XTRACEDEFAULTCOMP
#endif
#ifdef XGRTRACE
#define XGRTRACECOMP   int GRTrace;
#else
#define XGRTRACECOMP
#endif
#ifdef XMSCE
#define XMSCETRACECOMP   int  MSCETrace;
#else
#define XMSCETRACECOMP
#endif
#ifdef XMONITOR
#define XINTRANSCOMP   xbool InTransition;
#else
#define XINTRANSCOMP
#endif
#ifdef XMONITOR
#define XCALL_ADDR   int  CallAddress;
#else
#define XCALL_ADDR
#endif
#ifndef ULTRIXCC
#define XPAD_STRUCT_COMP \
        void (*RestartPAD) XPP((xPrsNode  VarP));
#else
#define XPAD_STRUCT_COMP  void (*RestartPAD) ();
#endif
#ifndef XNOUSEOFSERVICE
#define XSERVICE_COMP   xSrvNode ActiveSrv; xSrvNode 
SrvList;
#else
#define XSERVICE_COMP
#endif
#define PROCESS_VARS \
   xPrsNode       Pre; \
   xPrsNode       Suc; \
   int            RestartAddress; \
   xPrdNode       ActivePrd; \
   XPAD_STRUCT_COMP \
   XCALL_ADDR \
   XSERVICE_COMP \
   xPrsNode       NextPrs; \
   SDL_PId        Self; \
   xPrsIdNode     NameNode; \
   int            State; \
   xSignalNode    Signal; \
   xInputPortRec  InputPort; \
   SDL_PId        Parent; \
   SDL_PId        Offspring; \
   int            BlockInstNumber; \
   XSIGTYPE       pREPLY_Waited_For; \
   xSignalNode    pREPLY_Signal; \
   XPRSSENDERCOMP \
   XTRACEDEFAULTCOMP \
   XGRTRACECOMP \
   XMSCETRACECOMP \
   XINTRANSCOMP
typedef struct {
   xPrsNode      PrsP;
   int           InstNr;
   int           GlobalInstanceId;
}  xLocalPIdRec;
typedef xLocalPIdRec  *xLocalPIdNode;
typedef struct {
   int            GlobalNodeNr;
   xLocalPIdNode  LocalPId;
}  SDL_PId;
typedef struct xPrsStruct  *xPrsNode;
typedef struct xPrsStruct {
   PROCESS_VARS
} xPrsRec;
A PId value is thus a struct containing two components:

The use of the global node number is discussed in the chapter "The Application Library".

A xLocalPIdRec contains the following two components:

A xPrsRec struct contains the following components described below. As each yVDef_ProcessName struct contains the PROCESS_VARS macro as first item, it is possible to cast pointer values between a pointer to xPreRec and a pointer to a yVDef_ProcessName struct.

The Ready Queue, Scheduling

The ready queue is a doubly linked list with a head. It contains the process instances that can execute an immediate transition, but which has not been allowed to complete that transition. Process instances are inserted into the ready queue during output operations and nextstate operations and are removed from the ready queue when they execute the nextstate or stop operation that ends the current transition. The head in the ready queue, which is an object in the queue that does not represent any process but is inserted only to simplify the queue operations, is referenced by the xSysD component:

  xPrsNode   xReadyQueue;
This component is initiated in the function xInitKernel and used throughout the runtime library to reference the ready queue.

Scheduling of events is performed by the function xMainLoop, which is called from the main function in generated code after the initialization is performed.

void xMainLoop()
The strategy to have all interesting queues (the ready queue, the timer queue, and the input ports) sorted in the correct order is used in the library. Sorting is thus performed when an object is inserted into a queue, which means that scheduling is a simple task: select the first object in the timer queue or in the ready queue and submit it for execution.

There are several versions of the body of the endless loop in the function xMainLoop, which are used for different combinations of compilation switches. When it comes to scheduling of transitions and timer outputs they all have the following outline:

while (1) {
  if ( xTimerQueue->Suc->TimerTime <= SDL_Now() )
    SDL_OutputTimerSignal( xTimerQueue->Suc );
  else if ( xReadyQueue->Suc != xReadyQueue) {
    xRemoveFromInputPort(xReadyQueue->Suc->Signal);
    xReadyQueue->Suc->Sender =
       xReadyQueue->Suc->Signal->Sender;
    (*xReadyQueue->Suc->RestartPAD)(xReadyQueue->Suc);
  }
}
or, in descriptive terms:

while (1) {
  if ( there is a timer that has expired )
    send the corresponding timer signal;
  else if ( there is a process that can execute
            a transition ) {
    remove the signal causing the transition
      from input port;
    set up Sender in the process to Sender of
      the signal;
    execute the PAD function for the process;
  }
}
The different versions of the main loop handle different combinations of compilation switches. Other actions necessary in the main loop are dependent of the compilation switches. Example of such actions are:

Create and Stop Operations

A process instance is, while it is active, represented by the two structs:

These two structs are dynamically allocated. A PId value is also a struct (not allocated) containing two components, GlobalNodeNr and LocalPId, where LocalPId is a pointer to the xLocalPIdRec. Figure 555 on page 2172 shows how the xLocalPIdRec and the yVDef_ProcesName structs representing a process instance are connected.

Figure 555 : A xLocalPIdRec and a xVDef_ProcessName 
representing a Process instance. ----- (fig) -----
When a process instance performs a stop action, the memory used for the process instance should be reclaimed and it should be possible to reuse in subsequent create actions. After the stop action, old (invalid) PId values might however be stored in variables in other process instances.

If a signal is sent to such an old PId value; that is, to a stopped process instance, it should be possible to find and perform appropriate actions. If the complete representation of a process instance is reused then this will not be possible. There must therefore remain some little piece of information and thus some memory for each process instance that has ever existed. This is the purpose of the xLocalPIdRec. These structs will never be reused. Instead the following (see Figure 556) will happen when the process instance in Figure 555 on page 2172 performs a stop action.

Figure 556 : The memory structure after the process in Figure 555 has 
		performeda stop action. 
-----
(fig)  
       
       
-----
A new xLocalPIdRec is allocated and its PrsP references the yVDef_ProcessName (InstNr is 0). The Self component in the yVDef_ProcessName is changed to reference this new xLocalPIdRec. The old xLocalPIdRec still references the yVDef_ProcessName. The yVDef_ProcessName is entered into the avail list for this process type.

To reuse the data area for a process instance at a create operation it is only necessary to remove the yVDef_ProcessName from the avail list and update the InstNr component in the xLocalPIdRec referenced by Self.

Using this somewhat complicated structure to represent process instances allows a simple test to see if a PId value refers to an active or a stopped instance:

If P is a PId variable then the following expression:

P.LocalPId == P.LocalPId->PrsP->Self.LocalPId
is true if the process instance is active and false if it is stopped.

The basic behavior of the create and stop operations is performed by the functions SDL_Create and SDL_Stop.

void SDL_Create(
  xSignalNode  StartUpSig,
  xPrsIdNode   PrsId )
void SDL_Stop( xPrsNode  PrsP )
To create a process instance takes three steps performed in generated code:

  1. Call xGetSignal to obtain the start-up signal.
  2. Assign the actual process parameters to the start up signal parameters.
  3. Call SDL_Create with the start-up signal as parameter, together with the PrsIdNode representing the process to be created.
In xGetProcess the process instance is removed from the avail list of the process instance set (the component AvailPrsList in the PrsIdNode representing the process instance set), or if the avail list is empty new memory is allocated.

The process instance is linked into the list of active process instances (the component ActivePrsList in the PrsIdNode representing the process instance set). Both the avail list and the active list are single linked lists (without a head) using the component NextPrs in the yVDef_ProcessName struct as link.

To have an equal treatment of the initial transition and other transitions, the start state is implemented as an ordinary state with the name "start state" It is represented by 0. To execute the initial transition a "startup" signal is sent to the process. The start state can thus be seen as a state with one input of the startup signal and with save for all other signals. This implementation is completely transparent in the monitor, where startup signals are never shown in any way.

-------------------------------------------------------------
Note:                                                          
The actual values for FPARs are passed in the startup signal.  
-------------------------------------------------------------
Two IdNodes that are not part of the symbol table tree are created to represent a start state and a startup signal.

xStateIdNode   xStartStateId;
xSignalIdNode  xStartUpSignalId;
These xSysD components are initialized in the function xInitSymbolTable, which is part of sctsdl.c.

At a stop operation the function SDL_Stop is called. This function will release the signal that caused the current transition and all other signals in the input port. It will also remove all timers in the timer queue that are connected to this process instance by calling xRemoveTimer with the first parameter equal to 0. It then removes the process executing the stop operation from the ready queue and from the active list of the process type and returns the memory to the avail list of the current process instance set.

Output and Input of Signals

There are three actions performed in generated code to send a signal. First xGetSignal is called to obtain a data area that represents the signal instance, then the signal parameters are assigned their values and finally the function SDL_Output is called to actually send the signal. First in the SDL_Output function there are a number of dynamic tests (check if receiver in TO-clause is not NULL and not stopped, check if there is a path to the receiver). If the output does not contain any TO-clause and the C Code Generator has not been able to calculate the receiver, the xFindReceiver function is called to calculate the receiver according to the rules of SDL.

Next, in SDL_Output signals to the environment are handled. Three cases can be identified here:

  1. The environment function xOutEnv is called.
  2. The corresponding function that sends signals via the SDT communication mechanism (xOutPM) is called.
  3. The signal is inserted into the input port of the function representing the environment (xEnv).
Finally, internal signals in the SDL system are treated. Here also three cases can be identified (how this is evaluated is described last in this subsection):

  1. The signal can cause an immediate transition by the receiver.
  2. The signal should be saved.
  3. The signal should be immediately discarded.
If the signal can cause an immediate transition, the signal is inserted into the input port of the receiver, and the receiving process instance is inserted into the ready queue.

If the signal should be saved, the signal is just inserted into the input port of the receiver.

If the signal should be discarded, the function xReleaseSignal is called to reused the data area for the signal.

When a signal is identified to be the signal that should cause the next transition by the current process instance (at an Output or Nextstate operation), the component Signal in the yVDef_ProcessName for the process is set to refer to the signal. The signal is still part of the input port list.

When the transition is to be executed, the signal is removed from the input port in the main loop (see "The Ready Queue, Scheduling" on page 2170) immediately before the PAD function for the process is called.

First in the PAD function, the parameters of the signal are copied to the local variables according to the input statement. In the ending Nextstate or Stop operation of the transition the signal instance is returned to the avail list.

Evaluating How To Handle a Received Signal

There are two places in the run-tim kernel where it is necessary to evaluate how to handle signals (input, save, discard,...).

This calculation is implemented in the run-time kernel function xFindInputAction.

typedef unsigned char xInputAction;
#define xNotInSignalSet  (xInputAction)0
#define xInput           (xInputAction)1
#define xSave            (xInputAction)2
#define xDiscard         (xInputAction)3
#define xEnablCond       (xInputAction)4
#define xPrioInput       (xInputAction)5
  xSignalNode  SignalId,
  xPrsNode     VarP,
  xbool        CheckPrioInput )
The parameters of this function is:

As a result the function should return:

After this last update the correct transition can be started by the scheduler by just calling the function referenced by RestartPAD, which the as first action performs switch RestartAddr and starts execute the input symbol.

Figure 557 : Data structure used to evaluate the xFindInputAction. 
-----
(fig)  
       
-----
The algorithm to find the InputAction, the RestartAddr, and the RestartPAD is as follows:

  1. Let ProcessId become yVarP->NameNode and let StateId become ProcessId->StateList[yVarP->State].
  2. In ProcessId->SignalSet find the index (Index) where SignalId->NameNode is found. If the signal is not found, this signal is not in the signal set of the process, and the algorithm terminates returning the result xNotInSignalSet.
  3. StateId->SignalHandlArray[Index] now gives the action to be performed. If this value is xEnablCond, then the function StateId->EnablCond_Function is called. This function returns either xInput or xSave.
  4. If the result from step 3. is xInput, the algorithm terminates returning this value. yVarP->RestartAddr is also updated to
    StateId->InputRef[Index], while yVarP->RestartPAD is updated to ProcessId->PAD_Function.
    If the result from step 3. is xSave, the algorithm terminates returning this value.
    If the result from step 3. is xDiscard and ProcessId->Super equal to NULL, then the algorithm terminates returning this value.
    If the result from step 3. is xDiscard and ProcessId->Super not equal to NULL, then we are in a process type that inherits from another process type. We then have to perform step 2. - 4. again, with ProcessId assigned the value ProcessId->Super and StateIs assigned the value StateId->Super.

Nextstate Operations

The nextstate operation is implemented by the SDL_Nextstate function, where the following actions are performed:

  1. The signal that caused the current transition (component Signal in the yVDef_ProcessName) is released and the state variable (component State in the yVDef_ProcessName) is updated to the new state.
  2. Then the input port of the process is scanned for a signal that can cause a transition. During the scan signals might be saved or discarded until a signal specified in an input is found. Priority inputs are treated according to the rules of SDL.
  3. If no signal that can cause a transition is found, a check is made if any continuous signal can cause a transition (see "Enabling Conditions and Continuous Signals" on page 2180). The process is thereafter removed from the ready queue.
  4. If any signal (or continuous signal) can cause a transition then the process is re-inserted into the ready queue again at a position determined by its priority, else if the new state contains any continuous signal or enabling condition with an expression that might change its value during the time the process is in the state (view, import...), the process is inserted into the check list (see also "Enabling Conditions and Continuous Signals" on page 2180).

Decision and Task Operations

Decision and Task operations are implemented in generated code, except for the Trace-functions implemented in the sctutil.c file and for informal and any decisions that uses some support functions in sctutil.c. A Decision is implemented as a C if-statement, while the assignments in a Task are implemented as assignments or function calls in C.

Enabling Conditions and Continuous Signals

The expressions involved in continuous signals and enabling conditions are implemented in generated code in functions called yCont_StateName and yEnab_StateName. These functions are generated for each state containing continuous signals respectively enabling conditions. The functions are referenced through the components ContSig_Function and EnablCond_Function in the StateIdNode for the state. These components are 0 if no corresponding functions are generated.

The EnablCond_Functions are called from the function xFindInputAction, which is called from SDL_Output and SDL_Nextstate. If the enabling condition expression for the current signal is true then xInput is returned else xSave is returned. This information is then used to determine how to handle the signal in this state.

The ContSig_Functions are called from SDL_Nextstate, if the component ContSig_Function is not 0 and no signal that can cause an immediate transition is found during the input port scan. A ContSig_Function has the following prototype:

void ContSig_Function_Name (
  void *, int *, xIdNode *, int *);
where the first parameter is the pointer to the yVDef_ProcessName. The remaining parameters are all out parameters; the second contains the priority of the continuous signal with highest priority (=lowest value) that has an expression with the value true. Otherwise <0 is returned here. The third and fourth is only defined the second parameter >=0; the third is the IdNode for the process/procedure where the actual continuous signal can be found and the fourth is the RestartAddress connected to this continuous signal.

If a continuous signal expression with value true is found, a signal instance representing the continuous signal is created and inserted in the input port, and is thereafter treated as an ordinary signal. The signal type is continuous signal and is represented by an SignalIdNode (referenced by the variable xContSigId).

The check list is a list that contains the processes that wait in a state where enabling conditions or continuous signals need to be repeatedly recalculated.

A process is inserted into the check list if:

  1. It enters a state containing enabling conditions and/or continuous signals and
  2. No signal or continuous signal can cause an immediate transition and
  3. One or several of the expressions in the enabling conditions or continuous signals can change its value while the process is in the state (view, import...)
The component StateProperties in the StateIdNode reflects if any such expression is present in the state.

The check list is represented by the xSysD component:

xPrsNode  xCheckList;
The behavior of enabling conditions and continuous signals is in SDL modeled by letting the process repeatedly send signals to itself, thereby to repeatedly entering the current state. In the implementation chosen here, nextstate operations are performed "behind the scene" for all processes in the check list directly after a call to a PAD function is completed, that is directly after a transition is ended and directly after a timer output. This is performed by calling the function xCheckCheckList in the main loop of the program.

View and Reveal

A view expression is part of an expression in generated code and implemented by calling the function SDL_View.

void * SDL_View (
  xViewListRec *VList,
  SDL_PId       P,
  xbool         IsDefP,
  xPrsNode      ViewingPrs,
  char *        Reveal_Var
  int           SortSize);
The SDL_View function performs a test that the view expression is not NULL, refers to a process in the environment, or to a stopped process instance. If no errors are found the address of the revealed variable is returned as result from the SDL_View function. Otherwise the address of a variable containing only zeros is returned.

Import, Export, and Remote Variables

For an exported variable there are two components in the yVDef_ProcessName struct. One for the current value of the variable and one for the currently exported value of the variable. For each exported variable there will also be a struct that can be linked into a list in the corresponding RemoteVarIdNode. This list is then used to find a suitable exporter of a variable in an import action.

An export action is a simple operation. The current value of the variable is copied to the component representing the exported value. This is performed in generated code.

An import action is more complicated. It involves mainly a call of the function xGetExportAddr:

void * xGetExportAddr (
  xRemoteVarIdNode RemoteVarNode,
  SDL_PId          P,
  xbool            IsDefP,
  xPrsNode         Importer )
RemoteVarNode is a reference to the RemoteVarIdNode representing the remote variable (implicit or explicit), P is the PId expression given in the import action and IsDef is 0 or 1 depending on if any PId expression was given in the import action or not, Importer is the importing process instance. The xGetExportAddr will check the legality of the import action and will, if no PId expression is given, calculate which process it should be imported from.

If no errors are found the function will return the address where the exported value can be found. This address is then casted to the correct type (in generated code) and the value is obtained. If no process possible to import from is found, the address of a variable containing only zeros is returned by the xGetExportAddr function.

------------------------------------------------------------------------
Note:                                                                     
The strategy for import actions is in one sense not equal to the model    
for import given in the SDL recommendation. An import action is           
in the recommendation modelled as a signal sent from the importing        
process to the exporting process asking for the exported value, and       
a signal with this value sent back again. The synchronization effects     
by this signal communication is lost in the implementation model          
we have chosen. Instead our model is much easier and faster and the       
primary part of the import action, to obtain the exported value, is the   
same.                                                                     
------------------------------------------------------------------------

Services

Data Structure Representing Services

A service is represented by a struct type. The xSrvRec struct defined in scttypes.h, is, just like xPrsRec for processes, a struct containing general information about a service, while the parameters and variables of the service are defined in generated code in the same way as for processes.

In scttypes.h the following types concerning procedures can be found:

#ifdef XMONITOR
#define XCALL_ADDR   int  CallAddress;
#else
#define XCALL_ADDR
#endif
#ifndef ULTRIXCC
#define XPAD_STRUCT_COMP \
        void (*RestartPAD) XPP((xPrsNode  VarP));
#else
#define XPAD_STRUCT_COMP  void (*RestartPAD) ();
#endif
#define SERVICE_VARS \
   xSrvNode       NextSrv; \
   xPrsNode       ContainerPrs; \
   int            RestartAddress; \
   xPrdNode       ActivePrd; \
   XPAD_STRUCT_COMP \
   XCALL_ADDR \
   xSrvIdNode     NameNode; \
   int            State; \
   XSIGTYPE       pREPLY_Waited_For; \
   xSignalNode    pREPLY_Signal; \
   XINTRANSCOMP
#ifndef XNOUSEOFSERVICE
typedef struct xSrvStruct *xSrvNode;
#endif
#ifndef XNOUSEOFSERVICE
typedef struct xSrvStruct {
   SERVICE_VARS
}  xSrvRec;
#endif
In generated code yVDef_ProcedureName structs are defined accord
ing to the following:

typedef struct {
   SERVICE_VARS
   components for FPAR and DCL
}  yVDef_ServiceName;
The components in the xSrvRec are used as follows:

Executing Transitions in Services

From the scheduler's point view, it is not of interest if a process contains services or not. It is still the process instance that is scheduled in the ready queue and the PAD function of the process that is to be called to execute a transition. The PAD function for a process containing services performs three different actions:

The structure for a PAD function for a process with services are as follows:

YPAD_FUNCTION(yPAD_z00_P1)
{
  YPAD_YSVARP
  YPAD_YVARP(yVDef_z00_P1)
  YPRSNAME_VAR("P1")
  LOOP_LABEL_SERVICEDECOMP
  CALL_SERVICE
/*-----
* Initialization (no START symbol)
------*/
  BEGIN_START_TRANSITION(yPDef_z00_P1)
  yAssF_SDL_Integer(yVarP->z002_Global,
     SDL_INTEGER_LIT(10), XASS);
  START_SERVICES
}
where LOOP_LABEL_SERVICEDECOMP and BEGIN_START_TARNSITION are empty macros, i.e. expanded to no code. The yAss_SDL_Integer statement in an assignment of a default value to a process variable.

The macro CALL_SERVICE is expanded to:

if (yVarP->ActiveSrv != (xSrvNode)0) {
  (*yVarP->ActiveSrv->RestartPAD)(VarP);
  return; \
}
that is to a call of the PAD function of service reference by ActiveSrv.

The macro START_SERVICE is expanded to a call to the function xStart_Services, which can be found in sctsdl.c.The function creates the service instances, sets up the ActiveSrv pointer for the process to the first service, and then schedules the process for a new transition. This means that the next action performed by the system will be the start transition by the first service instance. When the first service executes a nextstate or stop action in the end of its start transition, the process will be scheduled again to execute the start transition of the second service, and so on until all services in the process has executed its start transitions.

For ordinary transitions, i.e. reception of a signal, it is obvious from the code above that the ActiveSrv pointer is essential. It should refer to the service instance that is to be executed. When a signal is to be received by a process, it is the function xFindInputAction (in sctsdl.c) that determines how to handle the signal and if it is to be received, where is the code for that transition. This function now also determines and sets up the ActiveSrv pointer.

Procedures

Data Structure Representing Procedures

A procedure is represented by a struct type. The xPrdRec struct defined in scttypes.h, is, just like xPrsRec for processes, a struct containing general information about a procedure, while the parameters and variables of the procedure are defined in generated code in the same way as for processes.

In scttypes.h the following types concerning procedures can be found:

#ifndef ULTRIXCC
#define XPRD_STRUCT_COMP \
        xbool (*RestartPRD) XPP((xPrsNode  VarP));
#else
#define XPRD_STRUCT_COMP  xbool (*RestartPRD) ();
#endif
#define PROCEDURE_VARS \
   xPrdIdNode   NameNode; \
   xPrdNode     StaticFather; \
   xPrdNode     DynamicFather; \
   int          RestartAddress; \
   XCALL_ADDR \
   XPRD_STRUCT_COMP \
   xSignalNode  pREPLY_Signal; \
   int          State;
typedef struct xPrdStruct  *xPrdNode;
typedef struct xPrdStruct {
   PROCEDURE_VARS
}  xPrdRec;
In generated code yVDef_ProcedureName structs are defined according to the following:

typedef struct {
   PROCEDURE_VARS
   components for FPAR and DCL
}  yVDef_ProcedureName;
The components in the xPrdRec are used as follows:

In Figure 558 on page 2190 an example of the structure of yVDef_Procedurename after four nested procedure calls are presented. Note that procedure Q is declared in the process, procedure R and S in Q and T in S.

Figure 558 : Structure of yVDef_ProcedureName After Four Nested Procedure 
		Calls.
-----
(fig)  
       
       
-----
The SDL procedures are partly implemented using C functions and partly using the structure shown above. Each SDL procedure is represented by a C function, which is called to execute actions defined in the procedure. This function corresponds to the PAD function for processes. The formal parameters and the variables are however implemented using a struct defined in generated code. The procedure stack for nested procedure calls is implemented using the components StaticFather and DynamicFather, and does not use the C function stack.

Calling and Returning from Procedures

Procedure calls and procedure returns are handled by three functions, one handling allocation of the data areas for procedures:

xPrdNode  xGetPrd( xPrdIdNode  PrdId )
and two functions called from generated code at a procedure call and a procedure return:

void xAddPrdCall(
  xPrdNode  R,
  xPrsNode  VarP,
  int       StaticFatherLevel,
  int       RestartAddress )
void xReleasePrd (xPrsNode  VarP)
A procedure call in SDL is in C represented by the following steps:

  1. Calling xGetPrd to obtain a data area for the procedure.
  2. Assigning procedure parameters to the data area.
  3. Calling xAddPrdCall to link the procedure into the static and dynamic chains.
  4. Calling the C function modelling the SDL procedure, i.e. the yProcedureName function.
The parameters to xAddPrdCall are as follows:

The xGetPrd returns a pointer to an xPrdRec, which can then be used to assign the parameter values directly to the components in the data area representing the formal parameters and variables of the procedure. Note that IN/OUT parameters are represented as addresses in this struct.

A procedure return is in generated code represented by calling the xReleasePrd followed by return 0, whereby the function representing the behavior of the SDL procedure is left.

The function representing the behavior of the SDL procedure is returned in two main situations:

If 0 is returned then the execution should continue with the next SDL symbol after the procedure call, while if 1 is returned the execution of the process instance should be terminated and the scheduler (main loop) should take control. This could mean that a number of nested SDL procedure calls should be terminated.

To continue to execute at the correct symbol when a procedure should be resumed after a nextstate operation, the following code is introduced in the PAD function for processes containing procedure calls:

while ( yVarP->ActivePrd != (xPrdNode)0 )
  if ((*yVarP->ActivePrd->RestartPRD)(VarP))
    return;
This means that uncompleted procedures are resumed one after one from the bottom of the procedure stack, until all procedures are completed or until one of them returns 1, i.e. executes a nextstate operation, at which the process is left for the scheduler again.

Channels and Signal Routes

The ChannelIdNodes for channels, signal routes, and gates are used in the functions xFindReceiver and xIsPath, which are both called from SDL_Output, to find the receiving process when there is no TO clause in the Output statement, respectively to check that there is a path to the receiver in the case of a TO clause in the Output statement. In both cases the paths built up using the ToId components in the IdNodes for processes, channels and signal routes are followed. To show the structure of these paths we use the small SDL system given in Figure 559

Figure 559 : A Small SDL System 
-----
(fig)  
       
-----
During the initialization of the system, the symbol table is built up. The part of the symbol table starting with the system will then have the structure outlined in Figure 560 As we can see in this example the declarations in the SDL system are directly reflected by IdNodes.

-----------------------------------------------------------------------
Note:                                                                    
Each channel and signal route is represented by two IdNodes, one         
for each direction. This is also true for an unidirectional channel or   
signal route. In this case the signal set will be empty for the unused   
direction.                                                               
-----------------------------------------------------------------------

Figure 560 : The symbol table tree for the system in Figure 559. 
------
.(fig)  
        
------
Each IdNode representing a process, a signal route, or a channel will have a component ToId. A ToId component is an address to an array of references to IdNodes. The size of this array is dependent on the number of items this object is connected to. A process that has three outgoing signal routes will have a ToId array which can represent three pointers plus an ending 0 pointer.

In the example in Figure 559 on page 2193 and Figure 560 on page 2194 there is no branching, so all ToId arrays will be of the size necessary for two pointers. Figure 561 shows how the IdNodes for the processes, signal routes and channels are connected to form paths, using the components ToId. In this case only simple paths are found (one from P1, via SR1, C, SR2, to P2, and one in the reverse direction). The generalization of this structure to handle branches is straightforward and discussed in the previous paragraph.

Figure 561 : The Connection of ToId for the System in 
Figure 559 and Figure 560. ----- (fig) -----

The Type Concept in SDL-92

The probably most important new feature in SDL-92 is the introduction of the object oriented features, such as TYPE, INHERITS, VIRTUAL, and REDEFINED. Here we start by discussing process types.

For each process type the C Code Generator will generate:

In the PrsIdNode there is one component (Super) that will refer to the PrsIdNode for the process type inherited by this process type. As sons to a PrsIdNode, IdNodes for declaration that are common for all instantiation of the process type can be found. Examples of such IdNodes are: nodes for variables, formal parameters, signals, timers, procedures, states, newtypes, and syntypes. Any typedefs or help functions for such units are also treated in the process type.

The PAD function will be independent of the PAD function for a inherited type, each PAD function just implementing the action described in its process type.

A yVDef_ProcessName struct will on the other hand include all variables and formal parameters from the top of the inheritance chain and downwards. Example:

process type P1;
fpar f1 integer;
dcl  d1 integer;
...
endprocess;
process type P2 inherits P1;
fpar f2 integer;
dcl  d2 integer;
...
endprocess;
This will generate the following principle yVDef_... structs:

typedef struct {
  PROCESS_VARS
  SDL_Integer f1;
  SDL_Integer d1;
} yVDef_P1;
typedef struct {
  PROCESS_VARS
  SDL_Integer f1;
  SDL_Integer d1;
  SDL_Integer f2;
  SDL_Integer d2;
} yVDef_P2;
A pointer to yVDef_P2 can thus be casted to a pointer to yVDef_P1, if only the common component (in PROCESS_VARS) or the variables in P1 is to be accessed. This possibility is used every time the PAD function for an inherited process type is called.

Each process instantiation will all be implemented as a xPrsIdNode. The Super component in such an object refers to the process type that is instantiated. No PAD function or yVDef_... struct will be generated. As sons to the PrsIdNode for a process instantiation, only such object are inserted that are different in different instantiations. For a process instantiation this is the gates. For other types of information the process instantiation uses the information given for its process type.

A very similar structure when it comes to IdNodes generated for block types and block instantiations are used by the code generator. There will be a BlockIdNode for both a block type and for a block instantiation. As sons to a block type, nodes that are the same in each block instantiation can be found (example: signal, newtype, syntype, block type, process type, procedure). As sons to a block instantiation, nodes that are needs to be represented in each block instantiation can be found (example: block instantiation, process instantiation, channel, signal route, gate, remote definitions).

---------------------------------------------------------------------
Note:                                                                  
A block or process (according to SDL-88), that is contained in a       
block type or a system type, is translated as if it was a type and in  
stantiation at the same place.                                         
---------------------------------------------------------------------
A way to look at the structure of IdNodes in a particular system is to use the command Symboltable in the monitor system. This command prints the IdNode structure as an indented list of objects.

This page intentionally left blank

 
Table of Contents Next Chapter