The ModulaTor logo 7KB

The ModulaTor

Oberon-2 and Modula-2 Technical Publication

The ModulaTor
Erlangen's First Independent Modula_2 Journal! Nr. 5/Jun-1992 
_____________________________________________________________

Simplifying the Subject of Protection                                                              


From: Wolfgang Redtenbacher (chair of DIN NI-22.13) c/o Redtenbacher 
Software Benzstra~se 4, D-7253 Renningen (Tel.: (07159) 3697, Fax: 
(07159) 17047) 

To: All members of WG 13 

Subject: DIN position regarding the subject of Protection 

While standardizing Modula-2, WG 13 has extended the PIM concept of protected 
modules by introducing 6 new pervasives ENTER, LEAVE, PROT, PROTECTION, 
INTERRUPTIBLE and UNINTERRUPTIBLE in an attempt to handle protection also in 
multiprocessor (or similar) situations where a simple masking of interrupts is not 
sufficient to ensure e.g. mutual exclusion. The following paper sets out to show that with 
the new exception and termination syntax introduced at the Tuebingen meeting of WG 13, 
most of the earlier language extensions are no longer necessary to permit "safe" 
programming of monitors etc. and can be replaced by a simpler and less error prone 
approach on the line of the original PIM design. 

The proposal of this paper is to (a) make ENTER and LEAVE invisible to the user as in 
Wirth's "Programming in Modula-2" (PIM), thus removing them as pervasives, (b) 
remove the language extension PROT and make INTERRUPTIBLE and UNINTER- 
RUPTIBLE just symbolic constants for PIM style protection expressions that represent 
the lowest resp. highest protection permitted by the underlying system (thus making 
unnecessary an explicit type name PROTECTION as the suggested model does not use 
variables of protection type), and (c) handle the situations that lead to the wish for 
introducing the above pervasives by showing how those situations can be handled safely 
using a PIM-like approach and the new exception syntax introduced in Tuebingen. 

Wording 

For sake of clarity, the following text uses the wording "language implicit ENTER" and 
"language implicit LEAVE" to denote the compiler supplied meaning of the pervasives 
ENTER and LEAVE as per CD 10514-2 and the wording "UserDefEnter" and 
"UserDefLeave" to denote user redefined ENTERs and LEAVEs as per CD 10514-2. 

Protection Type 

In the suggested model, protection expressions that may appear in a module header are 
of an implementation defined type for which only the way of writing it and 2 operations 
need to be defined: A "language implicit ENTER" and a "language implicit LEAVE". None 
of these 2 operations need to be visible to (or redefinable by) the user. 

Every level 0 procedure (i.e. every procedure not defined within another procedure) in a 
protected module (i.e. a module with a protection expression in its header) is protected 
by having an automatic "language implicit ENTER" executed at the start of the procedure 
(i.e. before the first statement) and an automatic "language implicit LEAVE" being 
executed at the end of the procedure (i.e. before leaving the scope of the procedure by 
either RETURNing in any way or leaving the procedure due to an exception being 
passed to an outer scope). Non-level 0 procedures do not need this protection as they 
can neither be exported directly nor indirectly and can only be called from within 
procedures that are already protected. 

The exact effect of a "language implicit ENTER" is: (a) The active protection value is 
pushed onto a "protection stack" and (b) if the active protection level is not already 
satisfying the protection requested by the protection expression of the module containing 
the current procedure, the protection is increased in such a way as to satisfy both the 
previously active protection level and the newly requested protection level. (Depending 
on the environment, this may be done by using the larger/smaller of the 2 protection 
values or by ANDing/ORing protection bit-masks etc.) 

The exact effect of a "language implicit LEAVE" is: (a) Pop the top level protection value 
off the "protection stack" and (b) make this value the currently active protection value. 

It is noted that this model differs from CD 10514-2 in that every level 0 procedure of a 
protected module is protected automatically (not only the directly exported procedures). 
As ENTER and LEAVE cannot be redefined under the suggested model, the 
performance issue that lead WG 13 to only protect directly exported procedures is no 
longer an issue, thus leading to the above simplification and removing the need for user 
callable (default) ENTERs and LEAVEs. 

Due to the fact that (a) the user can never explicitly call the language implicit ENTERs 
and LEAVEs and (b) a "language implicit LEAVE" is always executed when the stack of a 
protected procedure is unwound (independent of whether the unwinding takes place in 
normal or exceptional state), an impairing of ENTERs/LEAVEs can never occur. 

The identifiers INTERRUPTIBLE and UNINTERRUPTIBLE are actually no longer 
needed to describe any part of the suggested protection model. They could be used, 
however, as symbolic constants for PIM style protection expressions to refer in a 
portable way to the lowest resp. highest protection permitted by the underlying system. It 
is noted that some operating systems have different levels of "maximum protection 
against interrupts" for systems programming (kernel mode) and application 
programming (user mode), thus the actual value of UNINTERRUPTIBLE might be 
modified by a pragma that switches the compiler between "application programming 
mode" and "systems programming mode". 

As the type name PROTECTION will never appear in a program (the suggested model 
has no variables of protection type nor explicit operations on type PROTECTION), the 
pervasive PROTECTION is suggested to be removed from the standard. 

Temporarily Lowering Protection 

Temporarily lowering the protection level within a procedure can be done in 2 ways: By 
calling the procedure COROUTINES.LISTEN or by doing a TRANSFER to another 
coroutine that is running at a lower protection level. 

COROUTINES.LISTEN is suggested to cause a temporary lowering of the protection 
level to the level of the calling environment, i.e. to be equivalent to a "language implicit 
LEAVE" followed by a "language implicit ENTER". This means that LISTEN (a) 
memorizes the active protection level, (b) sets the active protection level to the value at 
the top of the "protection stack" temporarily and (c) restores the memorized protection 
level again. This prevents a careless LISTEN in a library from creating a chaos for the 
caller of this library routine, as it can now only reduce the protection to the level set by 
the caller of the library routine. (A call to COROUTINES.LISTEN from inside an 
unprotected procedure is considered to be an error which should be reported by the 
compiler.) 

Should it be necessary under special circumstances to reduce the protection level below 
the level of the caller of the active procedure, this can still be done by TRANSFERing to a 
coroutine with a lower protection level. Usage of this feature should be discouraged 
however, as it creates "protection gaps" that may cause program bugs that are very hard 
to find (due to timing dependency) and usually do not show up in the test stage but only 
at the customer! (If a library routine uses this feature, its documentation should at least 
very clearly warn the user.) 

Calling an Unprotected Procedure From a Protected One 

Calling a procedure with a lower protection level from inside a procedure with a higher 
protection level no longer needs to be an exception as per CD 10514-2 as it does not 
lower the active protection level anymore. Lowering the active protection level can only 
be done by (a) leaving the scope of the protected procedure, thus causing a "language 
implicit LEAVE" to be executed, (b) calling COROUTINES.LISTEN or (c) TRANSFERing 
to another coroutine with a lower protection level. 

It has been pointed out by Roger Henry that under this model a called procedure might 
not be able to do its job due to too high a protection level being active (e.g. a procedure 
"GetNextKeyStroke" might have difficulty when all interrupts are turned off). This 
observation is correct. Such an occurrence is however considered to be an obvious 
program design error (the writer of a protected procedure knows the interrupts he turned 
off and should not call procedures that need those interrupts) and will anyway show up 
immediately on the first program test as the error occurs always and is not dependent on 
hard to find "protection gaps" that may depend on environment, system load etc. 

Local Modules and Protection 

For consistency among all types of modules, it is suggested that local modules without a 
protection expression in the module header are treated as unprotected modules, i.e. the 
procedures of this module and the module initialization are not automatically bracketed 
by "language implicit ENTER/LEAVEs". 

It is noted that this permits an unprotected procedure to be exported from such a local 
module and to be re-exported from the outer protected module, thus in effect exporting 
an unprotected procedure from a protected module. As the exported procedure in such a 
case stems originally from an unprotected module and thus should not need any 
protection, this is not considered to be a problem. (Note that calling an unprotected 
procedure from inside a protected one does not lower the active protection level.) 

Exact Range of Protection 

In a protected module with an initialization part, the "language implicit ENTER" is 
executed immediately before the first statement of the initialization, i.e. after the import 
list has been initialized. A "language implicit LEAVE" is automatically inserted after the 
last statement of the initialization. 

Likewise, if a "FINALLY" block exists in a protected module, it is automatically bracketed 
by such a pair of "language implicit ENTER" and "language implicit LEAVE". 

If an exception handler is specified (by an EXCEPT clause after the BEGIN part or the 
FINALLY part of the module body), no "language implicit ENTER" is inserted at its 
beginning (as the handler is always entered in an already protected state). However a 
"language implicit LEAVE" is automatically inserted after the last statement of the 
EXCEPT block to ensure the protection level is properly restored if the program "falls off 
the bottom" of the protection handler (which causes an automatic re-raising of the 
exception in an outer scope). 

As the EXCEPT clause of a module body only guards the initialization and finalization of 
the module (but not the procedures exported by the module), these rules guarantee a 
proper pairing of ENTERs/LEAVEs in all cases. 

Similar rules apply to the protection of level 0 procedures in a protected module: The 
"language implicit ENTER" is executed immediately before the first statement of the 
procedure, i.e. after any local module to the procedure has been initialized. (If the local 
module needs any protection, it can be protected separately in the same way as any 
non-local module.) After the last statement of the procedure (i.e. the statement which is 
followed by an EXCEPT clause or the END label of the procedure) a "language implicit 
LEAVE" is automatically inserted. 

If the procedure body contains an EXCEPT clause, it is treated in the same way as an 
EXCEPT clause in a module body: No "language implicit ENTER" is inserted before the 
first statement (as the handler is always entered in a protected state already), but a 
"language implicit LEAVE" is automatically inserted after the last statement of the 
handler to ensure restoration of the protection level in case the program "falls off the 
bottom" of the protection handler. 

Protection of Coroutines 

For coroutines created by a call to COROUTINES.NEWCOROUTINE, the same rules 
regarding protection are suggested as for a simple procedure call. This means that the 
initial protection of the created coroutine is identical to the active protection of the 
procedure calling COROUTINES.NEWCOROUTINE. If the coroutine procedure has its 
own protection (specified in the header of the encompassing module), a "language 
implicit ENTER" is automatically executed at the start of the procedure body raising the 
protection as needed (see section "Exact range of protection" above). If the coroutine 
procedure contains a dynamic local module, the initialization and finalization of this 
module is executed under the initial protection of the coroutine (unless the local module 
has a higher protection specified in its header). A call to COROUTINES.LISTEN (which is 
permitted only if the coroutine is protected, i.e. is a level-0 procedure of a protected 
module) temporarily lowers the protection of the coroutine to its initial value (active at the 
time of the call to COROUTINES.NEWCOROUTINE). 

As a consequence, the parameter "initprotection" of COROUTINES.NEWCOROUTINE 
should be removed from the standard as it is no longer needed. 

It has been pointed out by Don Ward that this model prevents a protected coroutine from 
ever dynamically creating (directly) another coroutine with a lower protection than its 
creator. This observation is correct and this behaviour is fully intended (see the 
discussion on LISTEN in "Temporarily lowering protection"). Should it really be 
necessary to create such a coroutine with a lower protection, this effect (the need for 
which I consider a program design error anyway) can still be attained by e.g. creating a 
protected and an unprotected coroutine at start up (in different modules) and later on 
TRANSFERing from the protected coroutine to the unprotected one which creates the 
dynamically needed new coroutine with the lower protection and TRANSFERs back to its 
"caller" (the necessary communication between the protected and the unprotected 
coroutine can be achieved via 2 global variables). 

User Redefinition of "ENTER" and "LEAVE" 

The protection type provided by an implementor will usually cover the masking of 
external interrupts (i.e. something that normally cannot be attained by other standard 
Modula-2 means). 

In certain situations this might not be enough to implement mutual exclusion. If mutual 
exclusion is implemented by e.g. setting/clearing semaphores or processor locks, the 
user will want to write his own protection procedures (called "UserDefEnter" and 
"UserDefLeave" in the following). 

As user written procedures can be called explicitely, the functionality of a "user redefined 
ENTER/LEAVE" is very easy to attain in the proposed model and does not need any 
language extension: Simply insert an explicit call to "UserDefEnter" resp. 
"UserDefLeave" at the exact positions where language implicit ENTERs/LEAVEs are 
inserted per the above section on "Exact range of protection"! 

Thus a simple protected procedure will need 2 additional lines: 

  PROCEDURE DoSomething;
    ...
    BEGIN
      UserDefEnter;
      ...
      UserDefLeave;
    END DoSomething;

If "UserDefLeave" is to be executed also in the case of an exceptional termination, 2 
more lines of code are needed: 

  PROCEDURE DoSomething;
    ...
    BEGIN
      UserDefEnter;
      ...
      UserDefLeave;
    EXCEPT
      UserDefLeave;
    END DoSomething;

In the extreme case of a protected procedure that contains an exception handler and a 
protected local module with both a finalization part and another exception handler, 9 
statements have to be added to the code (plus an additional call to "UserDefLeave" 
before every explicit RETURN or RETRY statement in the exception handler): 

  PROCEDURE DoSomething;
    MODULE LocalMod;
    IMPORT UserDefEnter, UserDefLeave;
      ...
    BEGIN (*LocalMod*)
      UserDefEnter;
        ...
      UserDefLeave;
    FINALLY
      UserDefEnter;
        ...
      UserDefLeave;
    EXCEPT
        ...
      UserDefLeave;
    END LocalMod.
    ...
    BEGIN (*DoSomething*)
      UserDefEnter;
      ...
      UserDefLeave;
    EXCEPT
      ...
      UserDefLeave;
    END DoSomething;

The Protection Model of CD 10514-2 vs. The Proposed Model 

The proposed protection model has 3 disadvantages compared with the CD 10514-2 
protection model: 

1. As PROT is removed altogether, constructs like the following are not possible under 
the proposed model: 

   IF PROT()=INTERRUPTIBLE THEN
     WriteString("red")
   ELSE
     WriteString("blue")
   END;

Assuming that such constructs are not very valuable, this omission of PROT is not 
considered to be a serious "loss of functionality". 

2. The author of a protected module who wants his own "ENTER" and "LEAVE" and does 
not need to disable hardware interrupt sources has to write at least 2 more lines of code 
per protected procedure. 

Bearing in mind that Prof. Wirth has created the protection mechanism of PIM not as a 
means to shorten program code but as a means to selectively disable interrupt sources 
that cannot be disabled by other Modula-2 constructs, this is not considered a big burden 
on the programmer. As the additional lines of code are simple and straightforward (see 
section "User redefinition of ENTER and LEAVE"), the saving of a few lines of code 
should not justify a major language change from PIM! 

3. Those programmers who consider dynamically creating an unprotected coroutine 
from within a protected one as "added functionality" (and not a bug), have to write a little 
bit of additional code to achieve their purpose. 

The proposed protection model has 6 advantages over the CD 10514-2 model: 

1. Under the proposed model, the module header and the import list of a module show 
pretty well whether the module is portable or not (non-portable things are marked by 
protection expressions or imports from SYSTEM). In the CD 10514-2 model, a program 
can use very low level features (ENTER, LEAVE, PROT etc.) without having to import 
anything! 

2. The proposed model is not a radical language change like CD 10514-2, but just a 
refinement of the original PIM approach. (The new syntax using FINALLY and EXCEPT 
is common to both models.) 

3. The proposed model avoids the problem of impaired use of ENTER and LEAVE as 
these procedures are never called by the user (except in the form of "UserDefEnter" and 
"UserDefLeave"). 

4. The proposed model avoids the problem of an incorrect protection level being active in 
a termination procedure due to a HALT called from within a protected domain (in which 
case LEAVE is not called according to CD 10514-2, section 7.4, Note 2). The proposed 
model always restores the protection level correctly. 

5. The proposed model avoids the problem of inadvertently exporting an unprotected 
level 0 procedure from a protected module by assigning it to an exported procedure 
variable and forgetting to call an explicit ENTER/LEAVE pair. (Note that in the proposed 
model no differentiation between directly and indirectly exported level 0 procedures is 
needed.) 

6. The proposed model permits a procedure with an exception handler to safely call third 
party library procedures that might be protected but without exception handlers of their 
own. Under the CD 10514-2 model, such a library call might unexpectedly "return" to the 
exception handler of the calling procedure in a protected state - maybe even nested 
several times. 

To write a "safe" library procedure in the presence of protection, the CD 10514-2 model 
requires every protected procedure to have an EXCEPT clause (to ensure correct 
pairing of ENTER/LEAVE). In the proposed model the compiler supplied protection is 
always correctly restored and EXCEPT clauses are required only for protected 
procedures with user defined protection semantics (see "UserDefEnter" and 
"UserDefLeave" above). 

Summary 

The proposed model is not "fool proof" as a hidden TRANSFER in a (third party) library 
module can still ruin the protection level expected by the client of the library. For the 
majority of programmers (who will not need to redefine ENTER and LEAVE) it 
guarantees, however, a proper restoration of the protection level in all cases (including 
exceptional termination) and thus makes the area of protection a little bit safer. (I have 
done a lot of real time programming in multi-tasking environments and disagree strongly 
with those people who always try to tell me: "In the area of protection and priority, all bets 
are off anyway.") 

"Loss of functionality" means to me "something can be done under one model which 
cannot be done at all (or not as safe) under the other model". According to this definition, 
the proposed model has no less functionality than the CD 10514-2 model except for the 
above mentioned "misuse" of PROT ("IF PROT()=INTERRUPTIBLE ..."). All the other 
features of the CD 10514-2 model can still be attained, but do require in some cases 
additional lines of code or a program restructuring and the use of additional 
TRANSFERs. Bearing in mind that this additional burden is needed only in rather rare 
cases, the gain in safety and simplicity for the average programmer should be rated 
higher than that. 

Therefore the following changes to CD 10514-2 are suggested: 

(a) Remove the pervasives ENTER, LEAVE, PROT and PROTECTION from the 
standard. 

(b) Make the type "PROTECTION" an anonymous implementation defined type similar to 
"interrupt-source". (It needs no variables, no relational operators and no other operators 
except the "language implicit ENTER" above which is some sort of "protection addition".) 

(c) Make INTERRUPTIBLE and UNINTERRUPTIBLE symbolic constants for whatever 
protection expressions are needed to refer to the lowest resp. highest protection 
available in the selected "compiler mode" (kernel or user mode). 

(d) Modify COROUTINES.LISTEN as specified above and remove the 4th parameter 
"initprotection" from COROUTINES.NEWCOROUTINE. (The modification to LISTEN 
actually has already been agreed to at the Tuebingen meeting and is listed here only for 
completeness.) 

(e) Remove the exception type "protException" as it is no longer needed under the 
suggested model. 

APPENDIX: 

How to Implement Automatic Unwinding of Protection Levels 

The following description is intended to give implementors an idea of how to implement 
an automatic unwinding of protection levels in the case of exceptionally leaving a 
procedure scope, without introducing runtime overhead for those programs that do not 
need protection and/or exception handling. 

Notes:
 

1. The procedure and variable names in the following are purely symbolic and are not 
intended to be visible in the language.
 

2. The following model is described using a stack growing downward but does not 
depend on it. 

Stack frame for procedures: 

Always:
 

1. return address
 

2. frame ptr of caller: can be omitted by an optimizing compiler if the called procedure 
needs no stack frame of its own (i.e. has no parameters or local stack variables) and has 
no protection or exception handler of its own 

The next 2 variables exist only if the procedure is protected: (space is reserved in the 
stack frame of a protected procedure; the variables are assigned values by the 
"language implicit ENTER") 

3. "PrevProtFrame": frame ptr of next outer protected procedure
 

4. "PrevProtValue": protection value that was active before the current procedure 
executed its "ENTER" 

The next 2 variables exist only if the procedure has an exception handler: (space is 
reserved in the stack frame of a procedure with an exception handler; the variables are 
assigned values by the implicit "PushHandler" which is executed at the start of the 
procedure body)
 

5. "PrevExceptFrame": frame ptr of next outer procedure with protection handler
 

6. "HandlerAddr": start address of exception handler for the current procedure 

Always:
 

7. Local variables as needed 

Additionally, 3 static variables are needed in the workspace of each coroutine: 

a) "CurrExceptFrame": frame ptr of innermost procedure with an exception handler 
(initialized to "NIL" at program start resp. coroutine creation)
 

b) "CurrProtFrame": frame ptr of innermost procedure that is protected (initialized to 
"NIL" at program start resp. coroutine creation)
 

c) "CurrProtValue": current protection value of coroutine (initialized to an implementation 
defined value at program start resp. to the protection of the caller of 
COROUTINES.NEWCOROUTINE in case of a coroutine creation) 

In implementations where the frame ptr points to the current top of frame (e.g. the 
position of the frame ptr of the caller), the offsets of the invisible stack variables 
"PrevProtFrame" and "PrevProtValue" within the frame are the same in every protected 
procedure. The offsets of the invisible stack variables "PrevExceptFrame" and 
"HandlerAddr" can vary depending on whether the procedure is protected or not, but are 
always known statically by the compiler and can also be determined dynamically (when 
unwinding the stack in an exceptional state) by checking if the current procedure is 
protected (i.e. if "CurrProtFrame" equals the value of the current frame ptr). 

In implementations where the frame ptr points to the bottom of the frame, the 
arrangement of the invisible stack variables can be easily shifted around to meet the 
same criteria. 

Actions executed at procedure entry: 

  a) Stack frame is set up as above ("PrevProtFrame", "PrevProtValue",
     "PrevExceptFrame", "HandlerAddr" are uninitialized at this time).

  b) If the procedure has any local module, the module body is executed.

  c) If the procedure is protected, a "language implicit ENTER" is executed:
       "PrevProtFrame" := "CurrProtFrame";
       "PrevProtValue" := "CurrProtValue";
       "CurrProtFrame" := <value of current frame ptr>;
       <determine new protection value needed according to "CurrProtValue"
        and the protection expression in the module header, and make this
        value the active protection level (="SetProtection")>;
       "CurrProtValue" := <new protection value>;

  d) If the procedure has an exception handler, install it ("PushHandler"):
       <initialize "HandlerAddr" (statically known at compile time)>;
       "PrevExceptFrame" := "CurrExceptFrame";
       "CurrExceptFrame" := <value of current frame ptr>;

Implementing COROUTINES.LISTEN:

  <set active protection level to "PrevProtValue">;
  <set active protection level to "CurrProtValue">;

Actions executed when leaving a procedure in normal state:

  a) If the procedure has an exception handler (statically known), uninstall it ("PopHandler"):
       "CurrExceptFrame" := "PrevExceptFrame";

  b) If the procedure is protected (statically known), execute a "language implicit LEAVE":
       <set active protection to "PrevProtValue">;
       "CurrProtValue" := "PrevProtValue";
       "CurrProtFrame" := "PrevProtFrame";

  c) Unwind the stack frame and return to the caller (= standard actions at procedure end).

 Falling off the bottom of an exception handler: 

If a program falls "off the bottom" of an exception handler (= re-raising the exception in 
an outer scope), this is handled by the assignment "CurrExceptFrame" := 
"PrevExceptFrame", followed by a call to a runtime system procedure like 
"FindExceptHandler" (see below). 

"FindExceptHandler": Unwind stack exceptionally in search of handler 

    WHILE <stack not totally unwound> AND
          ("CurrExceptFrame"<>current frame ptr) DO
      IF "CurrProtFrame"=current frame ptr THEN
        (*language implicit LEAVE:*)
        <set active protection to "PrevProtValue">;
        "CurrProtValue" := "PrevProtValue";
        "CurrProtFrame" := "PrevProtFrame";
      END;
      <unwind the stack frame of current procedure and activate the frame
      of its caller>;
    END;
    IF "CurrExceptFrame"=current frame ptr THEN
      (*exception handler found:*) <jump to "HandlerAddr">;
    ELSE
      <terminate exceptionally, i.e. continue with termination code>;
    END;

Protection, Modules and Exception

From:        Kees Pronk <kees@dutiba.tudelft.nl>
Subject: Protection, Modules and Exception
To:        sc22wg13-list
Date:        Mon, 23 Mar 1992 16:48:54 +0100

 Dear WG-13 members, 

The appearance of the DIN note on Protection was a motive to publish this note; for 
those members that cannot process Latex I will send it out by airmail too. Regards, Kees 
Pronk. 

A note on current WG-13 positions 

A discussion note 

Kees Pronk thanks, T.U. Delft, The Netherlands, March 23, 1992 

The following is not a complete and final paper, it may be seen as thinking aloud or as 
the subject of a conversation during the coffee break. It is also not meant as being 
offensive to anybody, although certainly some people can identify their viewpoints there 
is no intention of putting the blame somewhere. This note is not (yet) complete and 
perhaps will contain some errors; its main purpose is to convey the idea. 

A precursor of this note has been on my desk for several weeks now and has been 
discussed with a few WG-13 colleagues privately. I was advised not to send this note out 
because of the discussion it might provoke and because of the delay in the production of 
the CD it might effectuate. Although I had more or less accepted that, the recent 
appearance of The DIN-position regarding Protection has changed my mind on this as 
the DIN paper tries to solve one of the problems I also try to solve in this note. Without 
going into detail I consider the DIN-approach a step in the proper direction, but only a 
small step. 

1. What's the problem
 

A characteristic of the original M-2 language is that the language is static as far as 
technically possible: there is lexical scoping, static binding, all bindables (names) are 
available in compile time etc.. 

WG-13 has, since its conception, tried to introduce some extensions to the language 
(because of evolutions taking place in language concepts and use). Because there has 
been - because of some conservatism - almost a ban on the use of syntactic 
mechanisms for extensions to the language, people have gone to great lengths to use 
the module concept and therefore to use procedure calls (an approach which I will call 
dynamic in the sequel) to extend the language to fit their purpose. 

The first use of dynamics came with the introduction of type COMPLEX. Originally 
proposed as just another module it became clear that the required efficiency could not be 
reached and therefore a language change was needed. No syntax change was 
necessary, a change in the type system was sufficient. 

A second use of dynamics was with the protection mechanism: ENTER and LEAVE were 
devised. With ENTER and LEAVE came the difficulty of having them paired and having 
user programmed versions of those routines. 

The third use of dynamics was in the introduction of exception handlers where, 
according to the TU-1 model, exception handlers were dynamically pushed and popped. 
Also, the set of exception identifiers/values was made dynamic. Apparently, the notion 
of dynamics has invaded WG-13 thinking. There is no such notion in the original 
M-2-language, however. 

When it was announced just before the Tuebingen meeting that it was not possible to fix a 
semantics for exception handling and termination using the TU-1 model (D163, of which 
the introduction is still recommended reading), that announcement had to be interpreted 
as more than the mere fact, it was an indication that the dynamic approach had (once 
again) failed (although I realized much later). 

My main problem is that, while I do have some sympathy for the TU-1 approach (the 
language as defined by Wirth is sacred, let's not touch it), at the Tuebingen meeting a 
sway-over from a full dynamic approach to a half way dynamic/syntax approach has 
been going on but for exceptions only. It is this double half way position which I find not 
defendable from a theory of programming languages point of view. 

In the sequel I will try to explain why the dynamic approach, as used by many in WG-13 
is considered problematic by me. After that, I will try to explain which way to go for a 
solution. The solution outlined here is only a sketchy one, I do not have time to provide 
more than that now. I realize that all this comes at a very late stage of standardization 
and I blame myself for not having noticed earlier. 

On the political issue: I know that this note reopens discussions already held in 
Nottingham at the very first WG-13 meeting. I know that people will say "too late, we 
need the standard just now, or, never ending M-2 standardization". However, in my 
opinion, the current WG-13 position is putting M-2 so far apart from other, competing 
languages (and therefore at a disadvantage) that I cannot keep silent. 

I am very worried about this!  

I am very worried that a standardized M-2-language will be effectively a dead language 
because: 

- The language is not being implemented because implementors do not understand the 
rationale behind the design, 

- The language will not be chosen for new developments because (in particular) the 
exception handling mechanism doesn't look attractive compared to other languages, 

- The language will no more be chosen for education because of the same reason, 

- Examples from the language will not be used in texts on programming languages (other 
than look at this!). 

My major worries are threefold: 

(i) the exception handling mechanism, 

(ii) some rules from programming language theory that are kind of broken in the 
current design, and, 

(iii) protection. 

After a short explanation on dynamics these three subjects are dealt with, followed by a 
proposed syntax and a solution. The motivation behind writing this note is that I believe 
that almost the same functionality as in TU-1 can be obtained using a few extensions to 
the syntax of M-2, thereby making the language much more attractive. 

If nobody will join the discussion, the minimum I hope to have achieved is explaining my 
point of view. 

Let me repeat: this is NOT intended to be offensive to anybody. 

2. Language design and dynamics
 

A language designer has the choice of using either syntax or dynamics. With the choice 
go a set of advantages/disadvantages. An advantage of the dynamic approach is the 
obtainable expressiveness and flexibility. However, this is also the main disadvantage. 
As procedures do have a special status in a language they often are too powerful for the 
intended use and can lead to brain-twisters such as the following: 

- ENTER as a procedural parameter,
 

- ENTER having side-effects: printing "hello world",
 

- recursive use of ENTER and LEAVE. 

Furthermore, dynamics easily leads to the addition of just one more procedure, 
satisfying a special demand, or opening up internals of the M-2 runtime system. The first 
gives the impression of a language with appendages, the latter is considered 
inappropriate for a high level language. 

Syntactic constructs on the other hand are restrictive; give a precise indication of their 
functionality and are easily learned. 

It can be argued that the weighting of one against the other has never been done 
explicitly in WG-13. 

The use of dynamics in the above way is considered by me as not being in the spirit of 
M-2; there are no such mechanisms in the original language (except for coroutines, 
which do have a special status anyway). The use of dynamics is also considered by me 
not being in the spirit of modern thinking about language design for the following 
reasons: 

- High level languages are called high level because they are designed in such a way 
that there exists a clear mapping from semantics to syntax (this is not the case for the 
dynamic approach), 

- Protection and exceptions are language features, and therefore it is natural to integrate 
them with the syntax of the language, 

- Integrating new language features with the syntax of a language may be considered 
cheap given modern compiler technology, 

It might be argued that the dynamic approach is on the one hand too powerful but on the 
other hand too weak (for true language extensions). A full syntactic approach will be 
developed in this note. Please note that wherever I am using a particular syntax I am not 
after precisely that syntax, such is a separate subject.
 

In designing things this way the following has been used as a guide-line: If a requested 
feature cannot be expressed using a modest syntax extension to PIM-M-2 then such a 
feature will be left out. 

3. Exceptions
 

Summarizing my earlier e-mail on exceptions: 

- The current position of WG-13 sets M-2 apart from any other language having 
exceptions I know of (Eiffel, Ada, M-3, C++future) and therefore at a disadvantage. In 
order to use the exception handling model of TU-1/TU-13 one has to use/teach/learn a 
module and some 10 procedures and the mechanism behind the overlapping 
enumeration type. 

- The TU-13 approach does not provide the ability to guard sequences of statements. 

A fully syntactic approach requires language extensions such as exception declarations 
and a special statement/keyword to test which exception has occurred e.g. as follows: 

  PROCEDURE X;
    EXCEPTION e1,e2;
    VAR y : INTEGER;
  BEGIN
    ...
  EXCEPT_CASE_OF
    e1 : StatSeq1 |
    e2 : StatSeq2
  ELSE          (* optional CATCH ALL *)
    ...
  END;

Local blocks (an old Algol-60, well established, easy to implement idea) are introduced to 
provide guarding of a StatSeq as follows: 

   BLOCK
     EXCEPTION e5;
   BEGIN
     ...   (* statseq to be quarded *)
   EXCEPT_CASE_OF
     e5: ....
   END;

This mechanism also allows to remove RETRY as follows:

  LOOP
    BLOCK
      EXCEPTION e7;
    BEGIN
      ...
    EXCEPT_CASE_OF
      ...
    END;
  END;

Language exceptions can be considered predefined, pervasive ( and redefinable)
e.g.:

  EXCEPTION indexExc, caseExc, .... ;

Exception identifiers can be declared, imported/exported, used in RAISE statements and 
EXCEPT_CASE_OF constructs. The dynamic semantics is similar to the TU-1/TU-13 
model. 

As a consequence of the above, the whole of the exception module can be removed and 
as there is no reason for the existence of the termination module then, it can be removed 
too, moving the procedures from the latter into Standard Funcs & Procs. 

Exception handling along the above lines is computationally complete in the sense that 
it gives all the basic functionality needed. Adding more (modules, messages) can be 
considered excess luggage for a mean and lean language. 

The problem is that as soon as modules and messages come into play there is a need 
for a type; then, some special rules and procedures creep in and then people are asking 
where do these procedures come from? OK, we need a system module 
M2ExCePtIoNs. As a result, the improvement to TU-1/TU-13 will become small. There 
seems to be no middle way between the minimal approach sketched above and opening 
Pandora's box. 

Please note that the above contains my personal ideas on exception handling. The 
exception handling group headed by John Lancaster is currently working on a proposal 
that may turn out being different. 

4. Modules and language theory
 

Generally, modules are considered being scope delimiters; that is modules are compile 
time constructs that do not (need to) have run-time equivalents. A module can be 
syntactically transformed away by the following elimination trick: 

  PROCEDURE X                  PROCEDURE X
    VAR Y : INTEGER;             VAR Y : INTEGER;
    MODULE Z;
    EXPORT Q
    VAR Q : REAL;                    Q : REAL;
    BEGIN               ==>    BEGIN
      CodeOfZ                      CodeOfZ;
    END Z;
  BEGIN
    CodeOfX;                       CodeOfX;
  END X;                       END X;

This trick is, as far as I know, what is done in most compilers and also in the VDM-SL. 
Although there is a slight difficulty (coroutines) in M-2, even the level zero modules can 
be eliminated this way; they disappear into the imaginary procedure surrounding the 
outermost modules. 

4.1 New concepts introduced
 

Whenever I am reading the following code: 

    KEYWORDa
      StatsAtoB
    KEYWORDb
      StatsBtoC
    KEYWORDc

it is my intuitive understanding that StatsAtoB and StatsBtoC are at the same lexical 
level. Whatever happens at KEYWORDb (calling a section of code; switching context) it 
is undone as soon as the StatsBtoC are executed. The explanation of several features of 
the exception/termination model do not fit this understanding and moreover introduce 
several concepts new to programming language theory. 

4.1.1. Code duplication
 

The following explanation of exception handling/termination does not fit the above 
model: 

Any module that contains an exception handler can be transformed 
into two procedure each containing identical exception handlers.  
For example the module:

     MODULE A;
     BEGIN
       Module_Init_Statement_Sequence
     FINALLY
       Module_Finally_Statement_Sequence
     EXCEPT
       Module_Except_Statement_Sequence
     END A

Can be transformed into the two procedures:

     PROCEDURE Init_A;
     BEGIN
       Module_Init_Statement_Sequence
     EXCEPT
       Module_Except_Statement_Sequence
     END Init_A;

     PROCEDURE Final_A;
     BEGIN
       Module_Finally_Statement_Sequence
     EXCEPT
       Module_Except_Statement_Sequence
     END Final_A;

This explanation introduces a new concept in programming language theory: a section of 
code is written only once but must be considered existing in two places. As an aside it 
might be noted that one exception handler serves two masters here. 

4.1.2. The notion of blocks
 

Basic to the understanding of programming languages is the notion of (local) blocks. 
Using a block construct entails: 

- being in a context
 

- entering a block (a new context)
 

- executing statements
 

- returning to the outer context 

Two intuitive understandings go with the idea of a block: 

1. that the outer context that is returned to is precisely the one existing on entering the 
block (except for side-effects introduced on purpose)
 

2. that all elements in the block are handled on an equal basis. 

The first of these understandings is violated by the Tuebingen decision that on returning 
from an exception in exceptional state LEAVE is not executed. The second one is 
violated by the decision that initial statements of dynamic modules will not be guarded by 
the exception handler of the outer procedure. Given the following situation: 

   PROCEDURE B;
     PROCEDURE C;
     BEGIN
       C_Stats
     END C;
     MODULE M;
     BEGIN
       M_Init_Stats
     FINALLY
       M_Final_Stats
     END M;
  BEGIN
    C;
  EXCEPT
    B_Except_Stats
  END B;

an un-handled exception, occurring while processing the C_Stats *will* be processed by 
the B_Except_-Stats, whereas an un-handled exception occurring while processing the 
M_Init_Stats will *not* be processed by the B_Except_-Stats; in spite of B and M 
occurring at the same lexical level. 

If I am correct, two intuitive laws on blocks are broken. 

4.1.3. The dichotomy of finalization vs termination
 

Finalization is the inverse of initialization; side-effects created during initialization may be 
undone during finalization. Given the current model, executing finalization code may be 
done again and again by repeatedly calling a procedure containing a module 
declaration. Finalization can exist in any module that is not at level 0.
 

Termination code is executed only once; it may only occur at one place in a program text 
(after the keyword FINALLY in a level 0 module). 

Given the difference in behaviour it seems wise to use different syntax for both 
constructs.
 

Saying the same in another way: 

- an exception section precisely looks as what it is: an exception;
 

- a finalization section looks like an exception section but isn't (it is weaker);
 

- termination looks like an exception but is stronger (an exception that cannot be 
handled). 

Said otherwise: a new language concept has been introduced: syntactic similar 
constructs having different semantics. 

4.1.4. Termination
 

On a call of HALT the termination code of level 0 is entered. This introduces a kind of 
longjump as a new concept in M-2. As an aside it might be noticed that although 
termination has been given a semantics, even when exercised in the presence of 
multiple coroutines, I haven't seen a useful, non-trivial example of the use of the 
mechanism. 

4.1.5. The completion principle
 

Given the possibility of textually removing all dynamic modules we are left with a 
language containing only procedures as active elements. It is considered strange 
(incomplete) that a procedure cannot have a finalization/termination section and cannot 
have protection. Please note that I do not advocate the removal of dynamic modules 
(modules local to a procedure) from the language. 

4.1.6. Conclusion
 

Several new concepts have been introduced into M-2. Most of these concepts can be 
regularized using local blocks. I am not so sure about termination; although (some) 
people are trying to persuade me that the syntax chosen for finalisation will do for 
termination too, I am afraid that two concepts are being forced into one syntactic 
construct. As the discussion on termination is currently still going on in the subgroup on 
termination/exception handling I will say no more about it now. 

5. Protection
 

In a language with protection there are two uses of protection: 

- to enable a piece of code to run at a certain priority,
 

- to enable the construction of monitors, that is data protected by mutual exclusion. 

Both are run-time constructs and have, in principle, nothing to do with modules; the 
module fits the combination but such is not strictly necessary. It can be considered an 
omission in the original language that an arbitrary code sequence, or the code sequence 
of a procedure cannot be protected. 

A similar indication that it is a code sequence that is to be protected is provided by the 
change WG-13 made to the NEWCOROUTINE procedure. This procedure now contains 
a protection parameter indicating the protection level at which the procedure is going to 
run. A next indication that a code sequence has to be protected is the existence of the 
(short) code sequence LISTEN. Summarizing: there are currently four ways to indicate 
protection: 

- the square brackets in the module headers,
 

- ENTER and LEAVE,
 

- LISTEN,
 

- NEWCOROUTINE. 
 
These mechanism are not always interchangeable/expressible in each other.
 

ENTER and LEAVE have three additional problems: 

- they are normally provided in pairs which is not easily enforceable,
 

- they can be redefined by the user,
 

- their explanation is cumbersome. 

A solution to this problem is (again) the introduction of a local block; extended with a 
protection indication: 

  BLOCK[MYLEVEL]
    ....
  BEGIN
    ....
  END;

Now, 

- ENTER and LEAVE can be removed, and with them the need for pairing, 

- there is only one syntactical expression for indicating priority: the square brackets, 

- there is no need for LISTEN anymore; it can easily be composed using local blocks with 
protection, although it may stay in the language for compatibility reasons. 

- Procedures can now have protection by: PROCEDURE X(...)[MYLEVEL]; Introducing 
this change may allow for the extra NEWCOROUTINE parameter to be removed. 

- As the protection level is statically determinable now, there is no need for the special 
rule on leaving a routine with an exception. 

Summarizing: removing ENTER and LEAVE and introducing local blocks and 
procedures with protection expressions removes a number of contentious constructs 
and regularizes the language.
 

Note that a similar proposal is in TU-17 by Mutylin and that a remark in this direction was 
made by someone from Germany (Wolfgang?) on Friday morning in Tuebingen.
 

and has now been materialized in The DIN position regarding Protection 

6. Syntax
 

The before mentioned constructs could be implemented using the following still sketchy 
syntax: 

BLOCK [OptProt];  | MODULE name [OptProt];| PROCEDURE name(..)[OptProt];
  decls           |   decls               |   decls
BEGIN             | BEGIN                 | BEGIN
  initcode        |   initcode            |   initcode
 EXCEPT_CASE_OF   |  EXCEPT_CASE_OF       |  EXCEPT_CASE_OF
  initexceptcode  |   initexceptcode      |   initexceptcode
FINALLY           | FINALLY               | FINALLY
  finalcode       |   finalcode           |   finalcode
 EXCEPT_CASE_OF   |  EXCEPT_CASE_OF       |  EXCEPT_CASE_OF
  finalexceptcode |   finalexceptcode     |   finalexceptcode
END;              | END name;             | END name;

 Generated by the following grammar (modulo some semicolons): 

BlockDecl        ::= 'BLOCK' OoboeSeq ';' .

ModuleDecl       ::= 'MODULE' id OoboeSeq id ';' .

ProcedureDecl    ::= 'PROCEDURE' id paramlist OoboeSeq id ';' .

OoboeSeq         ::= OptProt ';' OptDecls BeginSeq OptFinallySeq 'END' .

OptProt          ::= Empty | '[' ConstExpression ']' .

OptDecls         ::= Empty | Decls .

BeginSeq         ::= 'BEGIN' StatSeq OptExceptSeq .

OptFinallySeq    ::= Empty | 'FINALLY' StatSeq OptExceptSeq .

OptExceptSeq     ::= Empty | 'EXCEPT_CASE_OF' CaseLikeSeq .

StatSeq          ::= StatSeq Stat | Stat .

Stat             ::= Empty | Assign | IFstat | ... | BlockDecl .  <<<<<<

Decl             ::= CONST     {ConstantDeclaration ";"}
                  |  TYPE      {TypeDeclaration     ";"}
                  |  VAR       {VariableDeclaration ";"}
                  |  EXCEPTION {ExcDeclList         ";"}          <<<<<<
                  |  ProcedureDecl ";"
                  |  ModuleDecl    ";" .

ExcDeclList      ::= ExcDeclList "," ExcDecl | ExcDecl .

ExcDecl          ::= id .

Definition       ::= (* similar to Decl *)

CaseLikeSeq      ::= ExcCaseList  ExcTail .

ExcCaseList      ::= ExcCaseList "|" ExcCase  | ExcCase .

ExcCase          ::= Empty | ExcCaseLabeList ":" StatSeq .

ExcCaseLabelList ::= ExcCaseLabelList "," ExcCaseLabels
                   | ExcCaseLabels .

ExcCaseLabels    ::= ConstExpression .

ExcTail          ::= END | ELSE StatSeq END .
 

7. Conclusion
 

It is argued that it should be investigated if all current extensions to M-2 that are 
formulated using dynamics can be withdrawn and replaced by a well known syntactic 
construct: the local block. This holds in particular for: 

- the use of an extension to local blocks for exception handling and 
finalization/termination (see my sept '91 mailing and subsequent Ada-9X mailing); 

- the replacement of ENTER and LEAVE by local blocks with a protection expression; 

- the completion of the language such that also procedures can have both protection 
expressions and exception/finalization handlers. 

In this way only a few syntactical constructs having consistent explanation and notation 
need to be added to the language. 

A preliminary design as given in this note shows that a considerable increase in 
conceptual clarity can be obtained that way. Note that only a small amount of semantic 
changes (mostly simplifications) have been made to the TU-1/TU-13 proposals. 
Changes to the VDM will be limited, although some simplifications and reshuffling may 
be in order. 

It is my believe that the resulting language is much more in the spirit of M-2 and also 
looks more integrated than the current TU-1/TU-13 approach. 

Addendum: Re the DIN paper:
 

A rough comparison between the approach suggested here and the approach used in 
the DIN-paper: 

- Local blocks are being used for protection here; this cleans up even more. 

- I would suggest the following rule:
 

IF a procedure or a block or the code of a module has no protection expression and does 
not have a lexically enclosing construct (proc, module or block) having one,
 

THEN it runs at the level of its caller;
 

ELSE it runs at the indicated protection level. 

- I see no direct need for removing the protection levels
 

UNINTERRUPTIBLE .. MYLEVEL .. INTERRUPTIBLE
 

I agree with being silent about the type of these identifiers. 

- If the functionality provided by local blocks is not sufficient, one should go outside the 
language as in the DIN approach. 

- All six advantages of the DIN approach hold in my approach too. The three 
disadvantages mentioned in the DIN paper must not be considered really 
disadvantages. 

- Kees Pronk                                
- Delft University of Technology                  
- Fac. of Techn. Math. and Informatics              
- P.O. Box 356, NL-2600 AJ Delft, The Netherlands     
- Phone: +31-15782518, Fax: +31-15-787141               

Lector/PS file: eml:iso_exce.dw2, 11-Apr-1992 edited by 100023.2527@compuserve.com
-----------------------------------------------------------------------------------

H2O 
---
for VAX/VMS 

H2O  is a new ModulaWare product which belongs to the Moderon-family of 
programming languages :-)  

H2O is a native-code Oberon-2 compiler for DEC VAX/VMS. The back-end of H2O 
consists of the proven code-generator of ModulaWare's Modula-2 compiler MVR. 

H2O produces VAX/VMS object code files with VMS-debugger information; as 
with Modula-2, debugger mode/language is Pascal. In the first version of H2O it is 
only possible to import Oberon-2 modules in Modula-2 via so-called foreign 
definition modules, because H2O can't process Modula-2 symbol-files. Oberon-2 
symbol files are produced from marked objects in the implementation; there is no 
equivalent of Modula-2's definition module in Oberon-2. 

The first field-test version of H2O will be available in May-1992. 

If interested, please contact me at E-mail: 100023.2527@compuserve.com 

(TM) H2O, MVR and Moderon are trademarks of ModulaWare 
 

__________________________________________________________________________________________________ 


IMPRESSUM: The ModulaTor is an unrefereed journal. Technical papers are to be taken as working papers and personal rather than organizational statements. Items are printed at the discretion of the Editor based upon his judgement on the interest and relevancy to the readership. Letters, announcements, and other items of professional interest are selected on the same basis. Office of publication: The Editor of The ModulaTor is Guenter Dotzel; he can be reached by tel/fax: [removed due to abuse] or by mailto:[email deleted due to spam]
  ModulaWare home page   The ModulaTor download    [Any browser]

Webdesign by www.otolo.com/webworx, 14-Jul-1998