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
![]()