Date: Fri, 1 Jun 90 11:01:37 PDT From: David Goldberg Subject: Modula-3 and floating-point Here is a list of 8 changes to the language report that I think will improve Modula-3's usefulness when used for floating-point applications. Comments are welcome. David Goldberg goldberg@parc.xerox.com (1) In addition to the types REAL and LONGREAL, add the type EXTENDEDREAL. Constants of type EXTENDEDREAL use 'x' in place of 'd' or 'e', that is 3.1x-3 is the extended constant 3.1*10**(-3). Extend the generic operators and functions (ABS, FLOAT, LONGFLOAT, FLOOR, CEILING, ROUND, TRUNC, MAX, MIN) to work on extended reals, and add EXTENDEDFLOAT to convert numbers to the EXTENDEDREAL type. Rationale: Why does Modula-3 have two floating-point types? I would guess it's because two types has been traditional for languages going back to FORTRAN. Back when FORTRAN was invented, computers had (at most) two hardware supported floating- point types. Many modern machines have hardware support for 3 different kinds of floating-point (8087 family has IEEE extended, Vaxes have H format). The EXTENDEDREAL type is harmless on machines with only two hardware types, but fatal for programmers that need to access extended precision instructions and want to program in Modula-3. Implementions on machines without hardware support for extended precision are allowed to make EXTENDEDREAL identical to LONGREAL (this can be tested by comparing Real.precisionLONG with Real.precisionEXTENDED). Alternatives: Some FORTRAN implementations use 'q' instead of 'x' for floating-point constants (the 'q' for 'quad'). I prefer 'x' since it covers both IEEE implementations using the extended format (which can be as small as 80 bits) as well as 128 bit quad implementations. (2) There should be a required interface Real giving the range of floating-point numbers (other items for Real are mentioned later). CONST base: INTEGER; (* the base used for floating-point numbers * maxPos: REAL; minPosNormal: REAL; minPos: REAL; (* minPos # minPosNormal means there are denormals *) precision: INTEGER; maxPosLONG: LONGREAL; minPosNormalLONG: LONGREAL; minPosLONG: LONGREAL; precisionLONG: INTEGER; maxPosEXTENDED: EXTENDEDREAL; minPosNormalEXTENDED: EXTENDEDREAL; minPosEXTENDED: EXTENDEDREAL; precisionEXTENDED: INTEGER; Rationale: Although it is true that there are programs (like MACHDEP) that will usually give you the parameters of the arithmetic of the machine you are on, these programs are only heuristics. Why should the programmer have to intuit these parameters, when the implementor of the compiler knows precisely what they are? Having floating-point parameters of this nature in the language is done in many other languages, such as Common Lisp and in ANSI C. Alternatives: The advantage of having these values be constants rather than functions is that the compiler can more easily optimize them out. The disadvantage occurs when binaries can run unchanged on different versions of a machine with different kinds of floating-point. I believe this situation is sufficiently rare that making them constants is the best approach. ANSI C calls the smallest positive floating-point number FLT_MIN, (no 'POS'), but I prefer to make it clear that minPos is not the most negative floating-point number. (3) Concerning the definition of functions that round, such as ROUND(), FLOAT() and LONGFLOAT(). Currently the report says that they round to nearest, with ties broken arbitrarily. Change this to say that they round according to the constant Real.RoundStyle, which is a member of the enumeration {IEEE, Vax, IBM370, Other}. The value of RoundStyle not only controls ROUND and FLOAT, but specifies how the results of the basic arithmetic operations +, -, *, and / are rounded. Rationale: There are some low-level languages that define semantics to be what the hardware does. For example, C doesn't specify whether the right shift operator is logical or arithmetic. Unlike these languages, Modula-3 is independent of hardware idiosyncracies. Or at least the integer part of it is. In contrast, the floating-point part of the report seems to give up on defining the semantics of REAL. Although there is no perfect solution to the problem of mapping different floating- point hardware to a high-level language, the vast majority of existing computers use one of {IEEE, Vax, IBM370}, and each of these arithmetics is well-documented. RoundStyle = IEEE means only means that the default is for operations to be round-to-nearest (with halfway cases rounded to even) as described in the standard. It does not imply anything about existence of other rounding modes, or behavior under exceptions. Alternatives: You can try to characterize floating-point arithmetic with various parameters, such as how many guard digits are used, and so on. I think this approach ignores the fact that most machines shipped today have IEEE arithmetic, and the vast majority are in the set {IEEE, Vax, IBM370}. An approach that tries to parametrize floating-point arithmetic is both less precise than this proposal, and also more complex. (4) Update the discussion of numeric conversions to include the following information. The 4 functions FLOOR, CEILING, ROUND and TRUNC implement the four different ways of rounding to an integer, and on IEEE machines are independent of the current rounding mode. The 4 functions FLOAT, LONGFLOAT, EXTENDEDFLOAT and FIX convert between numeric types. On IEEE machines they use the current rounding mode, on other machines they use round-to-nearest, with halfway cases rounded according to Real.RoundStyle. Rationale: The report needs to explain the interaction between conversion operations and rounding modes. I slipped in a new function FIX to make things orthogonal. (5) The required interface Real should also contain the items below. In particular, it is no longer required that integer overflow be a checked runtime error. TYPE (* this uses IEEE terminology, where an exception is something like overflow, whereas a trap is an exception handler *) Exception = {Invalid, Inexact, Overflow, Underflow, DivByZero, IntOverflow, IntDivByZero}; ExceptionVec: ARRAY Exception OF BOOLEAN; Behavior = {Abort, ForcedTrap, VoluntaryTrap, Ignore}; CONST Behaviors: ARRAY Exception OF Behavior; (* gives the behavior of the 6 exceptions on this machine *) EXCEPTION Failure; (* raised when when SetTrap doesn't apply *) Trap(Exception); (* raised for an exception e if Behaviors[e] = ForcedTrap OR (Behaviors[e] = VoluntaryTrap AND GetTrap(e) = TRUE) *) PROCEDURE SetTrap(exception: Exception, enable: BOOLEAN): BOOLEAN; (* Enable/disable a trap, return old state. This raises Failure unless Behaviors[exception] = VoluntaryTrap *) PROCEDURE SetTraps(flags: Real.ExceptionVec): Real.ExceptionVec; (* enable/disable all traps, return old state *) PROCEDURE GetTrap(exception: Exception): BOOLEAN; Rationale: (3) and (5) together flesh out the semantics of floating-point operations. Adding integer overflow the the list of exceptions recognizes the fact that many compilers (including the SRC compiler!) will have trouble implementing an integer overflow exception efficiently. (6) Extend MOD to real types using the definition MOD(x,y) = x - y*TRUNC(x/y). Rationale: Because MOD is a generic function, it can not be added in an interface. This definition corresponds to the one in FORTRAN. (7) The required Real interface should contain the following functions. PROCEDURE scalb(x: REAL; N: INTEGER): REAL; PROCEDURE scalbLONG(x: LONGREAL; N: INTEGER): LONGREAL; PROCEDURE scalbEXTENDED(x: EXTENDEDREAL; N: INTEGER): EXTENDEDREAL; (* returns x*(2**N), by exponent manipulation instead of computing an exponential. *) PROCEDURE ilogb(x: REAL): INTEGER; PROCEDURE ilogbLONG(x: LONGREAL): INTEGER; PROCEDURE ilogbEXTENDED(x: EXTENDEDREAL): INTEGER; (* Returns the exponent of x. More precisely, if x = y*b**N, where b = Real.base and 1.0 <= |y| < b, then ilogb = N. Similar to the IEEE logb, except that it always returns an integer, never a NaN. *) PROCEDURE nextafter(x, y: REAL): REAL; PROCEDURE nextafterLONG(x, y: LONGREAL): LONGREAL; PROCEDURE nextafterEXTENDED(x, y: EXTENDEDREAL): EXTENDEDREAL; (* returns the next representable neighbor of x in the direction towards y. If x = y then the result is x *) PROCEDURE copysign(x, y: REAL): REAL; PROCEDURE copysignLONG(x, y: LONGREAL): LONGREAL; PROCEDURE copysignEXTENDED(x, y: EXTENDEDREAL): EXTENDEDREAL; (* returns x with the sign of y *) Rationale: Just as basic tools for manipulating integers are in a required interface (the Word interface), so should the basic tools for manipulating floating-point numbers. The names of these functions are chosen to match the names of the recommended functions in the IEEE floating-point standard. Alternatives: It would be nice if these could be defined as generics. (8) Any implementation on a machine that supports IEEE arithmetic must provide the interface IEEE which contains CONST infinity: REAL; infinityD: LONGREAL; infinityX: EXTENDEDREAL; TYPE Rounding = {MinusInfinity, PlusInfinity, Zero, Nearest}; PROCEDURE SetRounding(mode : RoundingMode): RoundingMode; (* set new mode, return old value *) PROCEDURE GetRounding(): RoundingMode; PROCEDURE SetFlag(flag: Real.Exception, status: BOOLEAN): BOOLEAN; (* set new status for one flag, return old status *) PROCEDURE SetFlags(flags: Real.ExceptionVec): Real.ExceptionVec; (* set new status for all flags, return old status *) PROCEDURE GetFlags(): Real.ExceptionVec; (* IEEE recommended functions *) TYPE Classes = {SignalingNaN, QuietNaN, Infinity, Normal, Denormal, Zero}; PROCEDURE class(x: REAL): Classes; (* Never exceptional, even if x is a signaling NaN *) PROCEDURE finite(x: REAL): BOOLEAN; PROCEDURE finiteLONG(x: LONGREAL): BOOLEAN; PROCEDURE finiteEXTENDED(x: EXTENDEDREAL): BOOLEAN; (* returns true if x is strictly between -infinity and +infinity *) PROCEDURE isnan(x: REAL): BOOLEAN; PROCEDURE isnanLONG(x: LONGREAL): BOOLEAN; PROCEDURE isnanEXTENDED(x: EXTENDEDREAL): BOOLEAN; (* equivalent to x # x; returns TRUE if x is a NaN, FALSE otherwise *) PROCEDURE sign(x: REAL): [0..1]; PROCEDURE signLONG(x: LONGREAL): [0..1]; PROCEDURE signEXTENDED(x: EXTENDEDREAL): [0..1]; (* Useful for getting the sign of 0 *) Rationale: IEEE arithmetic is a de facto standard as well as an official standard. Users who exploit parts of the standard should be guaranteed that their code will run on any machine that implements the standard. Alternatives: Since some machines only support part of the standard, you might require the IEEE interface on machines that implement either rounding modes or exception flags, raising the Failure exception if one of these is not implemented. Since this interface is not required on all implementations, it is not possible to write code of the form "IF ieee THEN x ELSE y;". So an alternative would be to require the IEEE interface on any machine, and have it raise the Failure exception on non-IEEE machines. In this scheme, you would probably want to change the infinity constants into functions (so they can raise an exception when used on non-IEEE machines). I assumed that a more likely usage pattern would be to prepare two versions of a module, one importing IEEE and the other not, and then compile in the one appropriate for the machine. The IEEE standard requires that trap handlers return more than just the type of exception, so implementations may want to define an IEEE.Trap whose argument contains a RECORD with this extra data. However, this seemed too implementation dependent to be included here. It would be nice if these functions could be defined as generics. The IEEE standard recommended function class has 10 members, distinguishing between Normal and NegNormal, Denormal, and NegDenormal, etc. I have followed both Sun and Apple in defining Classes to have only 6 members, and instead added the function sign(). ------------------------------------------------------------------------------ Date: Fri, 1 Jun 1990 14:58-EDT From: Donald.Lindsay@MATHOM.GANDALF.CS.CMU.EDU Subject: more floating point If Modula-3 is intended to be a language for serious floating point work, then surely it should have the "complex" data type(s). ------------------------------------------------------------------------------ Date: 3 Jun 1990 0057-PDT (Sunday) From: Subject: Re: Modula-3 and floating-point Thanks for David Goldberg for posting his thorough and sensible suggestions for Modula-3 floating point. There is little I can add, except some quibbles and questions, and a few dubious alternatives: ---------------------------------------------------------------------- CONST maxPos: REAL; On an IEEE machine, should this be plus infinity, or the largest finite REAL? In my limited experience, both constants are equally useful. Perhaps there should be two constants, maxPos and maxFinitePos; which may or may not be the same REAL value, depending on the machine. ---------------------------------------------------------------------- CONST maxPosLONG: LONGREAL; maxPosEXTENDED: EXTENDEDREAL; These belong to separate interfaces (LongReal and ExtendedReal). The advantage of having these values be constants rather than functions is that the compiler can more easily optimize them out. A more important reason for declaring those items as constants is that they are extremely useful as default values for formal parameters and record fields. ---------------------------------------------------------------------- (5) The required interface Real should also contain the items below. TYPE (* this uses IEEE terminology, where an exception is something like overflow, whereas a trap is an exception handler *) Exception = { Invalid, Inexact, Overflow, Underflow, DivByZero, IntOverflow, IntDiv ByZero }; ExceptionVec: ARRAY Exception OF BOOLEAN; Behavior = {Abort, ForcedTrap, VoluntaryTrap, Ignore}; EXCEPTION Failure; (* raised when when SetTrap doesn't apply *) Trap(Exception); (* raised for an exception e if Behaviors[e] = ForcedTrap OR (Behaviors[e] = VoluntaryTrap AND GetTrap(e) = TRUE) *) I am not sure there is any point in distinguishing between Abort and ForcedTrap. Does it have to do with "resumable" exceptions, in which the trap handler can supply a result and continue the operation? Modula-3 does not support such things, does it? In any case, if there is a reason to distinguish Abort from ForcedTrap, and Abort exceptions stop the program without raising Trap, they should be mapped to some other Modula-3 exception, which should be declared in the interface, too. Here is my rewriting of the above items, using a nomenclature that is hopefully more consistent with the Modula-3 jargon used in the report: TYPE Condition = { Invalid, Inexact, Overflow, Underflow, DivByZero, IntOverflow, IntDivBy Zero }; Conditions = SET OF Condition; Behavior = {AlwaysTrap, Optional, AlwaysIgnore}; EXCEPTION Error; (* Raised on client error. *) Exception(Condition); (* Raised for a condition c if IsEnabled(c) = TRUE) *) PROCEDURE GetBehavior(c: Condition): Behavior RAISES {Error}; (* Returns the behavior of this machine on condition c. *) PROCEDURE IsEnabled(c: Condition): BOOLEAN RAISES {Error}; (* Returns TRUE iff condition c will raise Exception. *) PROCEDURE EnableException( c: Condition; enable: BOOLEAN := TRUE ): BOOLEAN RAISES {Failure}; (* Enables/disables the raising of Exception for condition c, returns old enabled/disabled status. Raises Error if enable = TRUE and GetBehavior[c] = AlwaysIgnore, or enable = FALSE and GetBehavior[c] = AlwaysTrap *) PROCEDURE EnableExceptions(cs: Conditions): Conditions RAISES {Error}; (* Enables the raising of Exception for the listed conditions, and disables it for all other Optional conditions. Returns old set of enabled exceptions. Raises Error if the set cs includes any condition with behavior = AlwaysIgnore, or excludes any condition with behavior = AlwaysTrap. *) ---------------------------------------------------------------------- (6) Extend MOD to real types using MOD(x,y) = x - y*TRUNC(x/y). [...] This definition corresponds to the one in FORTRAN. Oops, surely MOD should be defined as x - y*FLOOR(x/y), to be consistent with the integer MOD. The latter's semantics was the outcome of a long and exhausting battle, in which the forces of Good, Mathematical Beauty, and Efficient Code Generation eventually defeated the dark forces of Evil, Ignorance, and Fortran Compatibility. I am sure you would rather accept FLOOR in the formula than wade through the complete annals of the battle, which I have dutifully saved, and which I am ready to dump onto this mailing list if you give me half a chance... 8-) ---------------------------------------------------------------------- (7) The required Real interface should contain the following functions. PROCEDURE scalb(x: REAL; N: INTEGER): REAL; PROCEDURE scalbLONG(x: LONGREAL; N: INTEGER): LONGREAL; PROCEDURE scalbEXTENDED(x: EXTENDEDREAL; N: INTEGER): EXTENDEDREAL; [...] Again, the LONGREAL and EXTENDEDREAL versions should be in separate interfaces. PROCEDURE nextafter(x, y: REAL): REAL; (* returns the next representable neighbor of x in the direction towards y. If x = y then the result is x *) It would be nice if these could be defined as generics. I heartily agree. More on this below. PROCEDURE copysign(x, y: REAL): REAL; PROCEDURE copysignLONG(x, y: LONGREAL): LONGREAL; PROCEDURE copysignEXTENDED(x, y: EXTENDEDREAL): EXTENDEDREAL; (* returns x with the sign of y *) I know this function is handy, but it feels quaint and ad-hoc---very fortranish. I would rather have the built-in generic function SIGN(r: T): [-1..+1], where T is any numeric type. I would also add to the Real interface the following procedures: PROCEDURE FRACTION(x: REAL): REAL RAISES {}; (* If x is zero, returns zero. If x is not zero, returns x scaled by an integral power of 2 such that the result is between 0.5 (inclusive) and 1.0 (exclusive) in absolute value. *) PROCEDURE EXPONENT(x: REAL): INTEGER RAISES {}; (* If x is zero, returns zero. If x is not zero, returns an integer k such that 2^k * x is between 0.5 (inclusive) and 1.0 (exclusive) in absolute value. *) ---------------------------------------------------------------------- The names of these functions are chosen to match the names of the recommended functions in the IEEE floating-point standard. Perhaps "Scale", "NextAfter", "CopySign", etc. would be more in line with the Orthodox Modula-3 Capitalization Style... ---------------------------------------------------------------------- (8) Any implementation on a machine that supports IEEE arithmetic must provide the interface IEEE I think it should be called IEEEReal or something like that. IEEE means much more than floating point... PROCEDURE finite(x: REAL): BOOLEAN; PROCEDURE isnan(x: REAL): BOOLEAN; PROCEDURE sign(x: REAL): [0..1]; I would rather have "IsFinite", "IsNaN", "SignBit". ---------------------------------------------------------------------- Some general comments: [1] As remarked before, it seems quite important that things like maxPosREAL be defined as constants, rather then variables or procedure calls. Unfortunately, Modula-3 does not support the notion of opaque constants, which means that the Real interface itself (as opposed to its implementation) will have to be machine-dependent. What is worse, it may even be impossible to write these interface cleanly in Modula-3. For example how does one declare the constant IEEEReal.MinusInfinity on a specific machine, say on the DS3100? The best solution I could think of is CONST MinusInfinity = LOOPHOLE(Word.Not(16_007fffff), REAL); which, besides being ugly, forces the interface to be UNSAFE. [2] One possible way to clean up some of this mess is extend LAST and FIRST to real numbers, and add to the language generalized versions of the SUCC and PRED operations of Modula-2. Specifically, the built-in operations LAST(T), FIRST(T), SUCC(v: T), and PRED(v: T) should be defined for any type T whose values are totally ordered by "<" and ">", including REAL, LONGREAL, and EXTENDEDREAL. FIRST(T) and LAST(T) should obviously return the smallest and largest value of the type T. SUCC(v) should return the smallest value u in T that is greater than v; and symmetrically for PRED(v). (SUCC and PRED should raise overflow if such values do not exist.) If these procedures were built-in, then many popular floating-point constants, including many of those requested by David, could be written directly as LAST(REAL), SUCC(0.0), SUCC(1.0)-1.0, and so on. Note that on an IEEE machine, LAST(REAL) is plus infinity, PRED(LAST(REAL)) is the largest finite real number, and SUCC(0.0) is the smallest positive (denormalized) number. [3] In the IEEE standard, there are various client-settable flags that affect the interpretation of floating-point operations. Are these flags automatically reset on procedure entry, or are they inherited from the caller? Are they automatically restored on procedure exit? Ideally, each thread in an address space should have its own set of IEEE flags. Is this a reasonable assumption, given the hardware that exists out there? [4] A more ambitious (and dubious) proposal is to incorporate IEEE floating point numbers into Modula-3 as a separate type, which would be a proper supertype of REAL. For the lack of a better name, I will denote this proposed type by "REALX". The type REAL would then include only the finite floating point numbers, while REALX would contain all of REAL plus the infinities and NaNs. Thus, LAST(REAL) would be the largest finite real, while LAST(REALX) would be plus infinity. On non-IEEE machines, the type REALX should be simulated by software, and thus would be somewhat inefficient: the compiler would have to reserve extra space for every REALX variable x, to encode possible infinities and NaNs; and the code for "x := x + 1.0" would have to catch overflow and convert it into an infinite result. However, this inefficiency could be avoided by declaring x to be REAL, which should allow most operations to be compiled into native floating-point operations. In this case, an overflow in the statement "x := x + 1.0" would be interpreted as a range fault: conceptually, the "+" operation, being defined on REALX, returns infinity, but this result cannot be stored back into x. [5] It is strongly desirable that operations such as FIRST, LAST, SUCC, SIGN, SCALE, EXPONENT, FRACTION, etc. be implemented as built-in functions, rather than as library procedures. This policy would make those functions a bit easier to use and would generally help making Modula-3 programs more readable and portable. Also, their implementation seems a natural part of the compiler's job, since it requires detailed knowledge of REAL representation. Moreover, including those functions in the language would force them to be better defined and documented, and would increase their chances of being correctly implemented, In addition, it would allow those functions to be used in environment that require constant expressions, such as defaults and CONST declarations. Finally, those functions are simple enough to be compiled in-line, and so fast that making them into procedure calls would significantly slow down many numerical computations. [6] I wish that REAL and LONGREAl were not two separate types. Having to write all those LONGFLOATs and "d0"s is quite annoying. For example, If I find that some REAL computations are being affected by roundoff, I cannot just change a few variable declarations from REAL to LONGREAL; I must read through the code, adding "d0"s here and there, and inserting LONGFLOATS and FLOATS everywhere. I think this is a price too high to pay just to save a few paragraphs in the report. So, I wish that REAL and LONGREAL had the same relation as, for example, INTEGER and BITS 8 FOR [-128..127]. In particular, REAL values should be a subset of the LONGREAL values. This would make it possible to assign any REAL expression to a LONGREAL variable. Moreover, it would imply that all floating-point operations are performed in double precision, which a good default rule as far as accuracy is concerned. One problem with this proposal is how to handle assignment of LONGREAL values (in particular, the result of arithmetic operations) to REAL variables. By analogy with the INTEGER/BITS FOR example above, the assignment x := 0.9 * x should include into a runtime test that the longreal value "0.9 * x" is indeed a REAL value, i.e., that the low-order half of its fraction is all zeros---which is almost never the case. Obviously, this is not the semantics one wants. Two possible solutions are (a) specifying that real := longreal is done by an implicit FLOAT; or (b) explicitly forbid the assignment real := longreal. Alternative (a) has lots of precedents, but seems dangerous and against the Modula-3 philosophy. Alternative (b) is conceptually a bit cleaner, but would require one to write explicit FLOATs in too many places, such as "x := FLOAT(0.9 * x)". One way to make (b) less objectionable is to replace FLOAT by a prefix operator ~, binding less tightly than any other arithmetic operation; then the example would be written x := ~ 0.9 * x. Similarly, one would write P(~ 0.9*x) to call a procedure P that takes a REAL parameter. If EXTENDEDREAL is added to the language, then one will also need an prefix operator for LONGFLOAT; perhaps ~~ ? An alternative to the prefix operator ~ is to have a special symbol :~ to denote "assignment with possible loss of precision"; one could then write x :~ 0.9 * x. This would remove the need for a distinct LONGFLOAT operator, since the target type would be specified by the left-hand side. In argument lists, one would use the notation :~ for keyword parameters, and ~ for positional parameters (the ~ here being a special syntactic hack for argument lists, not a prefix operator). [7] A relatively small but painful issue is how to handle the IEEE minus zero. The Modula-3 report defines "x=y" to be TRUE iff x and y are the same value; on the other hand, the IEEE standard says that minus zero compares equal to plus zero, but also says that the two values are not the same. I have no bright ideas on how this conflict should be solved. The same conflict arises in the definitions of "<", MAX, SIGN, etc. (Personally, I think that minus zero is a major blotch in the IEEE standard. The infinities and NaNs are already a bit too baroque, but at least they are merely extensions to the ordinary floating point operations, and their ugliness is largely confined to the remote ends of the REAL spectrum, far from the realms of everyday computation; whereas minus zero is an unsightly wart planted right in the middle of the most important numbers, needlessly complicating the semantics of floating-point operations in otherwise unexceptional cases. Minus zero has no counterpart in ordinary algebra, and no place at all in standard mathematics. No self-respecting mathematician would have anything to do with a system in which (-a) + (-b) is not the same as -(a + b); besides being ugly, this is exactly the kind of stuff that software bugs are made of. Minus zero is arguably the ugliest hack ever to find its way into a standard since TAB was made part of the ASCII character set. While I still hope to live long enough to see TABs stamped out of the face of the Earth, I fear that IEEE and its minus zero will survive undegraded in the environment longer than those plastic six-pack ties and styrofoam fast-food containers, and perhaps even as long as FORTRAN itself. So, regrettably, we cannot just ignore minus zero in the hope that it is just a bad dream that will be gone when we wake up in the morning. Someone will have to get its Modula-3 semantics sorted out, and I am only glad that it is not me... 8-) --jorge ------------------------------------------------------------------------------ Date: Sun, 3 Jun 90 08:01:05 -0400 From: MCCALPIN Subject: re: Modula-3 and floating-point It has been good to see the interest in floating-point arithmetic in Modula-3. My own interest in the language is as a portable replacement for FORTRAN. Now that reasonably effective C vectorizers are available, several new languages are candidates for large-scale scientific computing. The three that I can think of off the top of my head are Modula-3, C++, and TuringPlus. Of these three, TuringPlus and Modula-3 are closer to the philosophies that I approve of. I hope to have a paper ("A Comparison of TuringPlus versus Modula-3 for use in the Large-Scale Scientific Computing") finished by the end of the summer --- is anyone interested in this work? Back to the original topic, I have a short comment on Jorge Stolfi's recent message <9006030758.AA19253@jumbo.pa.dec.com>. >[6] I wish that REAL and LONGREAl were not two separate types. Having > to write all those LONGFLOATs and "d0"s is quite annoying. For > example, If I find that some REAL computations are being affected > by roundoff, I cannot just change a few variable declarations > from REAL to LONGREAL; I must read through the code, adding "d0"s > here and there, and inserting LONGFLOATS and FLOATS everywhere. > I think this is a price too high to pay just to save a few > paragraphs in the report. This is a problem that many of us just dealt with recently in the Fortran-8X review. The committee ended up adopting an alternative that solved approximately half of the problem. In Fortran-90 syntax, the user can now define the precision of a real number to be part of its "KIND" attribute. So we can write things like INTEGER, PARAMETER :: DBL=SELECTED_REAL_KIND(10,100) REAL, KIND=DBL, ARRAY(1024,1024):: PSI, Q, VORT, TEMP IMPLICIT REAL,KIND=DBL (A-H,O-Z) Where the first line says that "KIND" "DBL" is the closest available machine precision with at least 10 decimal digits of mantissa and an exponent range of at least 10^100. Then if all the variable are declared to be of type DBL, the precision of the code can be altered simply by changing the value of the SELECTED_REAL_KIND specifier. The second line declares a few 2D arrays in the new syntax. The third line is the old IMPLICIT statement, which would not be too strongly opposed to the Modula-3 philosophy if it did not declare anything (it does in Fortran) but simply provided a default precision if one were not explicitly provided. This solution is verbose (and is not very Fortran-ish), but it is workable. The problem of constants is not so cleanly addressed. In Fortran-90, one must append a precision specifier to all constants to force them to be able to take a user-specifiable precision: A(2) = 0.1_DBL ! DBL specified in previous example While this formally provides the needed functionality for new codes, it is irredeemably ugly, and provides no help for older codes full of constants. What is needed is a way for the user to specify what "default real" is. Here is where I get back to Modula-3. There is a need for the user to be able to specify what "real" is. With this specification enabled, I can write all my code with "real" variables and then modify "real" to mean "double" on my workstation and "float" on the Cray. > So, I wish that REAL and LONGREAL had the same relation as, for > example, INTEGER and BITS 8 FOR [-128..127]. In particular, REAL > values should be a subset of the LONGREAL values. This would make > it possible to assign any REAL expression to a LONGREAL variable. > Moreover, it would imply that all floating-point operations are > performed in double precision, which a good default rule as far > as accuracy is concerned. This would be a complete disaster on the Cray, for example, unless the Cray C compiler has the good sense to ignore all "double" casts. With the IEEE rounding rules for floating-point arithmetic, the old C tradition of calculating everything in double-precision is a very expensive way to get a very small increase in accuracy. > One problem with this proposal is how to handle assignment of > LONGREAL values (in particular, the result of arithmetic > operations) to REAL variables. By analogy with the INTEGER/BITS > FOR example above, the assignment x := 0.9 * x should include > into a runtime test that the longreal value "0.9 * x" is indeed > a REAL value, i.e., that the low-order half of its fraction is > all zeros---which is almost never the case. The obvious solution here is to type "0.9" according to its context. If 'x' is a longreal, then "0.9" should be coerced to longreal at compile-time. If `x` is real, then "0.9" is also real, and the calculation should be performed in single precision. > Obviously, this is not the semantics one wants. Two possible > solutions are (a) specifying that real := longreal is done by > an implicit FLOAT; or (b) explicitly forbid the assignment real > := longreal. Alternative (a) has lots of precedents, but seems > dangerous and against the Modula-3 philosophy. Alternative (b) > is conceptually a bit cleaner, but would require one to write > explicit FLOATs in too many places, such as "x := FLOAT(0.9 * x)". All floating-point math involves truncation of the results. It may be against the Modula-3 philosophy, but it is not avoidable. Without the implied cast of both arguments to double, the problem disappears. -- John D. McCalpin mccalpin@vax1.udel.edu Assistant Professor mccalpin@delocn.udel.edu College of Marine Studies, U. Del. mccalpin@scri1.scri.fsu.edu ------------------------------------------------------------------------------ Date: Sun, 3 Jun 90 15:02:58 PDT From: David Goldberg Subject: Re: Modula-3 and floating-point Thanks to Jorge for his comments. Here's the inevitable comments on the comments. On an IEEE machine, should this (Real.maxPos) be plus infinity, or the largest finite REAL? I had intended that IEEE.infinity is used to get infinity, Real.maxPos for the largest finite number. To avoid ambiguity perhaps it should be Real.maxFinitePos? I am not sure there is any point in distinguishing between Abort and ForcedTrap. Does it have to do with "resumable" exceptions, in which the trap handler can supply a result and continue the operation? Modula-3 does not support such things, does it? In any case,if there is a reason to distinguish Abort from ForcedTrap, and Abort exceptions stop the program without raising Trap, they should be mapped to some other Modula-3 exception, which should be declared in the interface, too. Oops, I forgot to explain what Real.Abort means. That was supposed to be for machines that are unable to map a floating-point exception into a Modula-3 exception. In other words, they can't resume excecution at the point of the exception handler. I have a minor quibble with your rewriting of the exception stuff in that I think GetBehavior should be a constant rather than a function. I would also add to the Real interface the following procedures: PROCEDURE FRACTION(x: REAL): REAL RAISES {}; (* If x is zero, returns zero. If x is not zero, returns x scaled by an integral power of 2 such that the result is between 0.5 (inclusive) and 1.0 (exclusive) in absolute value. *) PROCEDURE EXPONENT(x: REAL): INTEGER RAISES {}; (* If x is zero, returns zero. If x is not zero, returns an integer k such that 2^k * x is between 0.5 (inclusive) and 1.0 (exclusive) in absolute value. *) FRACTION was missing from my proposal (you needed to combine a scalb and logb to get it), but EXPONENT seems to be the same as ilogb (modulo a factor of 2). Or were you concerned about the value of ilogb(0) (which I forgot to define)? The name FRACTION is potentially ambiguous because the IEEE standard uses fraction to mean the part of the significand to the right of the binary point. I see a minor problem with extending SUCC and PRED to reals. If I want the constant which is the largest finite number, I have to say something awkward like IF (ieee_arith) THEN PRED(LAST(REAL)) ELSE LAST(REAL). I think it would be better to have LAST(REAL) always be the largest finite real, and have the constant infinity in the IEEE interface. Minus zero has no counterpart in ordinary algebra, and no place at all in standard mathematics. No self-respecting mathematician would have anything to do with a system in which (-a) + (-b) is not the same as -(a + b); besides being ugly, this is exactly the kind of stuff that software bugs are made of. Kahan has given several arguments for -0. One of them is that -0 is necessary for the identity 1/(1/x) = x to hold for all x (including x = -infinity). He has also argued that minus zero is just the right thing for complex arithmetic. I'm not endorsing this position, just pointing out that you can read about it in Kahan's article "Branch Cuts for Complex Elementary Functions" in the book "The State of the Art in Numerical Analysis", ed by M.J.D. Powell and A. Iserles, Oxford Univ Press, 1987. The simplest case of his idea is reproduced in my Xerox Tech Report "Floating-Point and Computer Systems", CSL-89-9. ------------------------------------------------------------------------------ Date: 3 Jun 1990 2029-PDT (Sunday) From: Subject: re: Modula-3 and floating-point Thanks to John McCalpin for describing the SELECTED_REAL_KIND feature of FORTRAN-90. Apart from the syntax, it seems a really good "programmer-friendly" feature. (And some people wonder why FORTRAN is still alive and kicking.) It would be nice if Modula-3 adopted this idea, too. Say TYPE Real = FLOAT(10, 100); or TYPE Real = FRACTION 10 EXPONENT 100 FLOAT; or TYPE Real = PRECISION 10 RANGE 100 FLOAT; This proposal still does not solve the problem of literals. In fact, it only makes it worse: presumably, one would have to write something like Real{0.9} or 0.9_Real to get a "0.9" of the right precision and type. John also writes [promoting all operands to double] would be a complete disaster on the Cray, for example, unless the Cray C compiler has the good sense to ignore all "double" casts. Well, the Modula-3 report does not say that LONGREAL has more precision than REAL... 8-) Seriously, wouldn't the introduction of explicit precision/range specs (see above) solve this problem, too? On a Cray, both REAL(5, 30) and REAL(10, 30) would be mapped to the C type "float"; on a 32-bit computer, they would be mapped into "float" and "double", respectively. The obvious solution [to compiling "x := 0.9 * x"] is to type "0.9" according to its context. If 'x' is a longreal, then "0.9" should be coerced to longreal at compile-time. If `x` is real, then "0.9" is also real, and the calculation should be performed in single precision. Alas, this may be a fine solution for FORTRAN or C, but I believe that the strict rules of the Modula-3 game exclude this solution. If I correctly understand the committee's position, (1) target-typing of expressions is "out" (2) the result of an arithmetic operation depends only on the *values* of the operands. Rule (1) means that the type of the right-hand side of an assignment cannot depend on the type of the receiving variable or parameter. More generally, the type of an operand cannot depend on the operation being applied to it. Rule (2) implies that operand attributes that do not affect its value (such as "packed" and "subrange") cannot affect the result of the operation. That is, packed and subrange operands are implicitly widened and/or unpacked to the corresponding base type. For example, the sum of two variables of type BITS 8 FOR [-128..127] is computed by widening each operand to INTEGER, and then performing INTEGER addition. Therefore, by analogy, if REAL values were a subset of the LONGREAL values, then all floating point operations would have to be peformed in the "base type" (LONGREAL, or perhaps EXTENDEDREAL). I guess that the chances of these rules being significantly modified are nil (assuming I got them right, of course.) Would the committee care to comment? Incidentally, many of the difficulties of integrating REAL and LONGREAL have nothing to do with floating point per se, but rather with the existence of two variants of the same base type, each with its own set of arithmetic operations, differing only in precision (and cost). A proposal to add the type LONGINTEGER to the current language would run into the same sort of difficulties. All floating-point math involves truncation of the results. It may be against the Modula-3 philosophy, but it is not avoidable. Good point. --jorge ------------------------------------------------------------------------------ Date: 3 Jun 1990 2029-PDT (Sunday) From: Subject: Re: Modula-3 and floating-point (And now, of course, some comments to the comments on the comments:) [Me:] On an IEEE machine, should [Real.maxPos] be plus infinity, or the largest finite REAL? [David Goldberg:] I had intended that IEEE.infinity is used to get infinity, Real.maxPos for the largest finite number. To avoid ambiguity perhaps it should be Real.maxFinitePos? I would rather have both Real.maxPos and Real.maxFinitePos, which would be identical on non-IEEE machines. The reason for having both constants is that the proper choice of "largest real" depends on the application. Consider the following typical uses: 1. initial value when computing MIN: min := Real.maxPos; FOR i := 0 TO LAST(v) DO v := MIN(min, v) END; 2. overflow avoidance: IF a >= Real.maxFinitePos/2.0 THEN x := Real.maxFinitePos ELSE x := 2.0 * a END; 3. interval representation: INTERFACE RealInterval; TYPE T = RECORD lo, hi: REAL END; (* The closed interval [lo..hi] *) CONST Full = T{-Real.maxPos, Real.maxPos}; (* All real numbers *) In example 1, one definitely wants the largest REAL, finite or not; in example 2, one definitely wants the largest *finite* real. In example 3 both choices are reasonable, but still not equivalent. Exporting both maxPos and maxFinitePos in the Real interface would make it possible to write portable code like the above, that works reasonably well on both IEEE and non-IEEE machines. Note that code that imports the IEEE interface is automatically non-portable. [David:] I think GetBehavior should be a constant rather than a function. Possibly. I was just trying to keep the interface machine-independent. EXPONENT seems to be the same as ilogb (modulo a factor of 2). Oops, right. I just failed to notice ilogb. The name FRACTION is potentially ambiguous because the IEEE standard uses fraction to mean the part of the significand to the right of the binary point. Rats. I thought FRACTION was such a nice name. How important is it to follow IEEE *nomenclature* (as opposed to functionality)? Oh, never mind... I see a minor problem ... If I want the constant which is the largest finite number, I have to say something awkward like IF (ieee_arith) THEN PRED(LAST(REAL)) ELSE LAST(REAL). I think it would be better to have LAST(REAL) always be the largest finite real, and have the constant infinity in the IEEE interface. Considering the Modula-3 philosophy that semantic simplicity is worth more than the programmer's convenience, I would say that LAST(REAL) can only be the largest legal REAL value (in the sense of ">"). Thus, the largest *finite* real would still have to be imported from an interface. (Incidentally, don't forget that Modula-3 has no conditional expressions. Therefore, even if you had LAST(REAL) and PRED and ieee_arith, you still could not write CONST maxFinite = IF ieee_arith THEN PRED(LAST(REAL)) ELSE LAST(REAL) END; But this is fuel for another flame...) Kahan has given several arguments for -0. One of them is that -0 is necessary for the identity 1/(1/x) = x to hold for all x (including x = -infinity). I have read some of Kahan's arguments, but I find them somewhat unconvincing. It is true that infinities, NaNs and minus zero simplify many numerical programs, but they also complicate many others. For example, -0 fixes 1/(1/x) = x, but breaks (-a) + (-b) = - (a + b). It is not clear that the net balance is positive. I wonder what the REAL (i.e., non-INTEGER) programmers out there think about minus zero. But I am straying away from the subject... --jorge ------------------------------------------------------------------------------ Date: 4 Jun 1990 1215-PDT (Monday) From: Subject: re: Modula-3 and floating-point Jorge's two principles are obeyed by current Modula-3 and I for one would be loath to give either of them up. Admittedly, as John McCalpin points out, all floating-point computations involve truncation of results. But it doesn't follow from this that implicit coercion upon assignment is good. ------------------------------------------------------------------------------ Date: 4 Jun 1990 1518-PDT (Monday) From: Subject: Re: (-a) + (-b) is not the same as -(a + b) Someone asked for an example where (-a) + (-b) is not the same as -(a + b) in IEEE floating-point. Here it is: srcpm5 20> ${M3COMPILER} -o Test499 Test499.m3 srcpm5 21> Test499 a = 10111111100000000000000000000000 b = 00111111100000000000000000000000 -(a + b) = 10000000000000000000000000000000 (-a) + (-b) = 00000000000000000000000000000000 srcpm5 22> cat Test499.m3 UNSAFE MODULE Test499 EXPORTS Main; IMPORT Wr, TextWr, Word; FROM Stdio IMPORT stderr; PROCEDURE Main() = VAR a, b, c, d: REAL; BEGIN a := -1.0; b := 1.0; c := -(a + b); d := (-a) + (-b); Wr.PutText(stderr, "a = " & BitsOf(LOOPHOLE(a, Word.T)) & "\n" & "b = " & BitsOf(LOOPHOLE(b, Word.T)) & "\n" & "-(a + b) = " & BitsOf(LOOPHOLE(c, Word.T)) & "\n" & "(-a) + (-b) = " & BitsOf(LOOPHOLE(d, Word.T)) & "\n" ); END Main; PROCEDURE BitsOf(w: Word.T): TEXT = VAR wr := TextWr.New(); BEGIN FOR i := 31 TO 0 BY -1 DO IF Word.Extract(w, i, 1) = 0 THEN Wr.PutChar(wr, '0') ELSE Wr.PutChar(wr, '1') END; END; RETURN TextWr.ToText(wr) END BitsOf; BEGIN Main() END Test499. srcpm5 23> --jorge ------------------------------------------------------------------------------ Date: 5 Jun 1990 0148-PDT (Tuesday) From: Subject: Re: Modula-3 and floating-point [David Goldberg:] ... my ilogb function ... always gives the "true" exponent (like your EXPONENT function). ... I think it makes more sense to have ilog(0) = FIRST(INTEGER) than to set it to zero, because it then makes ilog monotonic. Sounds reasonable. So I think we only disagree on whether the fraction part is normalized to be between 1/2 and 1 or between 1 and 2. There is also the issue of a name for FRACTION. I think that SIGNIFICAND is gradually gaining ground as the name for this thing. Either way is fine with me. --jorge ------------------------------------------------------------------------------ Date: Tue, 5 Jun 90 10:30:14 CDT From: Terry Klarich Subject: A question and a request Would it be possible to change my email address for the m3 mailing list from klarich@a.cs.okstate.edu to terry@unx.ucc.okstate.edu? I don't get on this machine as much as I used to. Also, I am aware of the m3 compiler which runs on some of the bsd dirived systems. Has anyone made or is planning to make the port to sysv? I sure would like to see this happen. ------------------------------------------------------------------------------ Terry Klarich (klarich@a.cs.okstate.edu) n5hts A man is not complete until he is married then, he is finished. ------------------------------------------------------------------------------ Date: Tue, 5 Jun 90 16:00:43 -0500 (CDT) From: Dick Orgass Subject: V 1.4 bug? As I read the top of p. 13 of the revised report, the attached small program is incorrectly rejected by the compiler because of the redeclaration of a field and a method. The error messages follow the program. The Olivetti compiler accepts and correctly translates the same program. Did I misread the report or is this a bug? If it's a bug, is there a fix in v. 1.5? Dick (* File Main2.m3, created by Orgass on 5-Jun-90. *) (* Last edited by Orgass on 5-Jun-90. *) MODULE Main2 EXPORTS Main; TYPE T = OBJECT ident: TEXT := "Top level object.\n"; METHODS print() := Print; END; Subtype = T OBJECT <==== Line 14 ident: TEXT := "Second level object.\n"; METHODS print() := Print2; END; PROCEDURE Print(t: T) = BEGIN END Print; PROCEDURE Print2(t: Subtype) = BEGIN END Print2; BEGIN END Main2. % m3 -c Main2.m3 "Main2.m3", line 14: duplicate field name (ident) "Main2.m3", line 14: duplicate method (print) 2 errors encountered m3: /usr/contrib/lib/m3/m3compiler exited, status=2 ------------------------------------------------------------------------------ Date: 5 Jun 1990 1643-PDT (Tuesday) From: Subject: Re: V 1.4 bug? Dick Orgass's program is correct. This is what is meant by the sentence A field or method in a subtype masks any field or method of the same name in the supertype. on pg. 13. (What else could this sentence mean?) ------------------------------------------------------------------------------ Date: 5 Jun 1990 1719-PDT (Tuesday) From: Subject: Re: V 1.4 bug? Yes, Dick's program is correct. Dick, did you realize that you're not overriding fields or methods, but rather adding new ones? A flattened version of your "Subtype" is: OBJECT <*HIDDEN*> ident: TEXT := "Top level object.\n"; ident: TEXT := "Second level object.\n"; METHODS <*HIDDEN*> print() := Print; print() := Print2; END; I'd guess that you meant to override the defaults with something like: TYPE Subtype = T OBJECT ident := "Second level object.\n"; METHODS print := Print2; END; except that field defaults cannot be overridden, so even this version creates a type with two "ident" fields. - Bill ------------------------------------------------------------------------------ Date: Tue, 05 Jun 90 17:29:35 PDT From: Eric Muller Subject: Re: V 1.4 bug? > on pg. 13. (What else could this sentence mean?) I have learned to be careful about the meaning of the sentences in the Modula-3 report. Conciseness is nice but it also means that there are no error-correcting sentences. It would be very nice to have a published "Modula-3 Rational (revised)". It would make the work of the committee more apparent to casual readers, help programmers in the Modula-3 adventure and guide implementers. May be for the next meeting of the committee ? Eric Muller. ------------------------------------------------------------------------------ Date: Wed, 06 Jun 90 09:07:20 +0100 From: mjj@computer-lab.cambridge.ac.uk Subject: Re: V 1.4 bug? > A field or method in a subtype masks any field or method of the > same name in the supertype. I have only had bad experiences with this feature. Its on my list for decomissioning. Mick ------------------------------------------------------------------------------ Date: Wed, 6 Jun 90 09:38:33 -0500 (CDT) From: Dick Orgass Subject: Re: V 1.4 bug? Many thanks for all the comments about my bug report. Bill -- I explicitly intended the first example that you gave in response; I wanted the additional field and method. The purpose of the example was to convince a C++ proponent who is at least casually looking at Modula-3 that one can do this sort of thing without doing table lookups. In this case, a simple code example is more persuasive than more abstract arguments. That's why I hope there's a fix in v 1.5. Mick -- I quite agree that masking fields and methods of a supertype is hard to implement and, at least in some cases, I would argue that it's bad programming to use it. On the other hand, there are cases where it's essential to for writing reasonable object oriented applications. This is the part of the language definition that makes it the case that one can think in this way: I want an object just like ... except I want something different for ... and still have an instance of the child be acceptable as an instance of the parent. In summary, I'm opposed to removing this part of the language definition in spite of the ambiguity that Bill pointed out. Dick ------------------------------------------------------------------------------ Date: 6 Jun 1990 1138-PDT (Wednesday) From: Subject: Re: V 1.4 bug? About the following sentence in the M3 manual (1) A field or method in a subtype masks any field or method of the same name in the supertype. Mick Jordan comments I have only had bad experiences with this feature. Its on my list for decomissioning. It would certainly be nice to forbid this from happening, but how? If the supertype is opaque, the coincidence of names must be allowed, since there is no way for the programmer who defines the subtype to avoid the name clash (pervertedly peeking at the concrete type won't help, since the type could change). Now the irritating question arises, what rules apply in a scope where both the subtype and the concrete supertype are visible? You either have to forbid this from happening---presumably by this rule: (2) It is illegal to import two interfaces if together they reveal that some type has a field or method name that coincides with a field or method name of one of its supertypes ---or you would have to roll with the punch and define what happens when both names are visible, presumably by the rule (1). The committee never liked (1) very well, but (2) seems worse. The main argument against it is that a module-level facility like Import should not be dependent on the details of the type system. A second argument is that one can imagine cases in which (2) might be overly restrictive. Suppose for example that Thread.Mutex has a field name in common with its subtype WrClass.T, which is perfectly possible. Now imagine some special-purpose thread performance monitor that needs the representation of Thread.Mutex to do its monitoring, and also needs to import WrClass in order to implement its logging. Rule (2) would prohibit importing the information that the tool needs. Rule (1) allows the import and specifies a rule for dealing with the resulting name conflict. ------------------------------------------------------------------------------ Date: Thu, 07 Jun 90 01:46:36 PDT From: Eric Muller Subject: Re: A question and a request Terry Klarich asks: > Also, I am aware of the m3 compiler which runs on some of the bsd dirived > systems. Has anyone made or is planning to make the port to sysv? I sure > would like to see this happen. I am not aware of anybody doing such a port. In any case, if you want to start a port now, it would be better to wait a week or so. Release 1.5 is being tested internally and it offers better support for doing ports. But if you are almost done, don't take that as an excuse 8-), I will take the burden of integrating modifications to 1.4 into 1.5. Eric Muller, DEC-SRC. ------------------------------------------------------------------------------ Date: Fri, 8 Jun 90 16:33:12 -0500 (CDT) From: Dick Orgass Subject: Programming Question: Sorting This question came up when I was yet again converting code for Floyd's Treesort 3 to Modula-3. The relevnat part of the specification is: Sort in place a homogenous array whose elements are a subtype of REFANY with respect to an order relation, >= . I also wanted to do this in the safe language. My first cut at doing it was the following incorrect declarations: TYPE GreaterEqualProc = PROCEDURE (lhs: REFANY; rhs: REFANY): BOOLEAN; (* Procedures of type GreaterEqualProc return TRUE if lhs >= rhs and FALSE otherwise. The actual parameters passed to this procedure will be elements of the array that is being sorted. It is the responsibility of client authors to assign the formal parameters to local variables of the appropriate type.*) PROCEDURE Sort (items: REF ARRAY OF REFANY; ge: GreaterEqualProc); This is wrong because the subtype rule for arrays (p. 15) prohibits passing, for example, a REF ARRYAY OF TEXT to the procedure. I reluctantly adopted the following solution. The first parameter of Sort was declared to be of type REFANY. The implementation is an UNSAFE module and uses a loophole to convert the actual parameter into a REF ARRAY OF REFANY. I believe there will be a checked runtime error if the first actual parameter is not a reference to an array of some traced reference type. Is there a better way to do this? If generics were added to the language, this would provide a safe way to do this at the cost of complexity for clients and extra runtime code so I don't like that solution. Dick ------------------------------------------------------------------------------ Date: 10 Jun 1990 1553-PDT (Sunday) From: Subject: Re: Programming Question: Sorting Dick Orgass observes that it is difficult to write a generic sorting routine in M3. If, for example, the sort routine takes a parameter of type REF ARRAY OF REFANY, then you cannot pass it a REF ARRAY OF TEXT, since, although it is true that TEXT <: REFANY, it is not true that REF ARRAY OF TEXT <: REF ARRAY OF REFANY, because the subtype rules do not contain either of the rules ARRAY OF T <: ARRAY OF U if T <: U (1) REF T <: REF U if T <: U (2) The trouble with rule (1) is that it would make the compiler more complicated. Consider, for example, these two variables: VAR a: ARRAY I OF INTEGER; b: ARRAY I OF BITS 8 FOR [0..255]; Rule (1) would allow a := b. Therefore, to implement the assignment, the elements of b have to be unpacked one by one and assigned into the elements of a. Even worse, rule (1) would allow b := a, with the compiler required to examine every element of a to check if it is in the range [0..255], and give a checked runtime error if it isn't. By excluding rule (1), we allow the compiler to implement array assignment by copying the data without massaging it. As for rule (2), it is completely unsound. Here's an example program: TYPE T = REF [0..9]; U = REF INTEGER; VAR t: T; u: U; ... u := t; (* Allowed by (2) *) u^ := 10; (* Now t^ = 10, contrary to t's type. *) So we can't have rule (2). An argument could be made for rule (1), but rule (1) alone would not solve Dick's problem: if the sorting routine took an ARRAY OF REFANY instead of a REF ARRAY OF REFANY, then it would have to be a VAR parameter instead of a VALUE parameter, and the types of the actual and formal for a VAR parameter must be identical. I therefore conclude that there is no hope for writing a safe sorting routine along the lines that Dick outlines in his message. I believe that generics are the best solution to the problem. Greg ------------------------------------------------------------------------------ Date: Sun, 10 Jun 90 20:51:20 -0400 (EDT) From: Zalman Stern Subject: How does one hide default methods? One of the people looking at Modula-3 here has leveled the criticism that when declaring an object, the default method bindings must be present in the interface where the methods are declared. This is undesirable since conceptually, the actual procedures that a method is bound to are part of the implementation of an object, not the interface. Also, one may not want to export these procedures. My first attempt at getting around this looks like so: INTERFACE TestObj; TYPE T <: BRANDED OBJECT foo: INTEGER; METHODS SetFoo(fooValue: INTEGER); GetFoo(): INTEGER; END; ... MODULE TestObj; REVEAL T = BRANDED OBJECT foo: INTEGER; METHODS SetFoo(fooValue: INTEGER) := SetFoo; GetFoo(): INTEGER := GetFoo; END; PROCEDURE SetFoo(self: T; fooValue: INTEGER) = ... PROCEDURE GetFoo(self: T): INTEGER = ... This results in an error since the type expression in the revelation is not a subype of the one in the original subtype declaration. I can see why this is although I'm not sure what implications structural type equivalence should have for object subtyping. (That is, are class names considered part of the structure on an object?) The second version of the code looks like so: INTERFACE TestObj2; TYPE U = OBJECT foo: INTEGER; METHODS SetFoo(fooValue: INTEGER); GetFoo(): INTEGER; END; TYPE T <: U; ... MODULE TestObj2; REVEAL T = U BRANDED OBJECT METHODS SetFoo := SetFoo; GetFoo := GetFoo; END; PROCEDURE SetFoo(self: T; fooValue: INTEGER) = ... PROCEDURE GetFoo(self: T): INTEGER = ... My objection to this is that it introduces the additional type U which has no purpose. The only difference between T and U is the specification of default methods. Having to carry around an extra type for every object type simply to hide the default method bindings is unpleasant at best. Another approach is to leave the object without default methods and create an allocation procedure which calls NEW with the appropriate method bindings. I do not like this approach since it requires an additional convention for allocation procedures and it does not place the default method bindings in any declaration of the object. There should be a complete declaration of the object (including default method bindings) available to the implementation. So am I missing something obvious? Anyone have any ideas on a better way to deal with this problem? Sincerely, Zalman Stern | Internet: zs01+@andrew.cmu.edu | Usenet: I'm soooo confused... Information Technology Center, Carnegie Mellon, Pittsburgh, PA 15213-3890 *** Friends don't let friends program in C++ *** ------------------------------------------------------------------------------ Date: Mon, 11 Jun 90 11:41:05 met From: Piet van Oostrum Subject: Re: A question and a request I sent this message to Eric Muller when I meant to send it to the list. On 07 Jun 90 Eric Muller wrote: > > Terry Klarich asks: > > > Also, I am aware of the m3 compiler which runs on some of the bsd dirived > > systems. Has anyone made or is planning to make the port to sysv? I sure > > would like to see this happen. > > I am not aware of anybody doing such a port. In any case, if you > want to start a port now, it would be better to wait a week or so. > Release 1.5 is being tested internally and it offers better support > for doing ports. But if you are almost done, don't take that as an > excuse 8-), I will take the burden of integrating modifications to 1.4 > into 1.5. As I have mentioned before on this list, I did a port to HP/UX 7.0 which is a kind of SYSV. However, I have one unsolved problem with regard to terminal I/O that I have also mentioned: The read from a terminal does not detect EOF. For this to be cleared up I would like to know what ioctl(0,FIONREAD, &n) returns on Ultrix when an end of file is given on the terminal. On our system it gives the same value (n=0) as on no input. Thus there is no way to tell that you could really do a read when EOF is given. The following code demonstrates this on our system. It gives the same output regardless whether you let it time out or give it an end-of-file. I don't know if the read with n=0 is legal on other Unixes. #include #include main() { int i,n; char buf[80]; sleep (10); /* gives you time to type ^D */ i = ioctl(0,FIONREAD, &n); printf ("i=%d, n=%d\n", i, n); i = read(0, buf, n); printf ("i=%d, n=%d\n", i, n); } Actually I would like to suggest an alternative solution for the context switching of threads based on the availability of I/O. Now this is done by means of the FIONREAD stuff, but this works only for input. A thread that is blocked on output (e.g. to a terminal that sends XOFF) will hold up other threads (if I understand it correctly). It would be more general to keep track of threads that want to do I/O and in the thread dispatcher test the runnability of the threads with select(). This has the additional advantage that it also works for output channels, and I think it works on more Unixes. The disadvantage is that the various modules are going to be more intertwined, i.e. it will be more difficult to keep things separated. By the way, I would like single threaded programs not to be bothered with all the Threads stuff (this also makes them easier to be debugged), but the above proposal makes that even harder. Are there plans to factor the Threads stuff out out the kernel? Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Mon, 11 Jun 90 23:57:20 PDT From: Eric Muller Subject: Re: A question and a request > However, I have one unsolved problem with regard to terminal > I/O that I have also mentioned: The read from a terminal does not detect EOF. > For this to be cleared up I would like to know what ioctl(0,FIONREAD, &n) > returns on Ultrix when an end of file is given on the terminal. On our > system it gives the same value (n=0) as on no input. Thus there is no way > to tell that you could really do a read when EOF is given. When EOF is reached, ioctl sets n to 1 and read returns 0 in i. Thus EOF can be detected. However, typing "abc^D" when reading from the terminal does not work (just as the same input to cat(1) does not terminate cat). Actually read: - returns 0 on EOF - returns -1 and sets errno to EWOULDBLOCK if nothing is present and O_NDELAY or O_NONBLOCK is set - blocks otherwise If your version of read does the same thing, one solution is say that 'ioctl' sometime lies and to try the read with O_NDELAY if ioctl sets n to 0. Then either read returns 0 and ioctl was wrong or read returns -1 and ioctl was right. Remember that UltrixFileStream (or UFileRdWr in 1.5) is a system specific implementation as it calls the operating system. If we need to support different versions of it, fine. In 1.5, I fixed reads from /dev/null, by the way (in 1.4, that produces a runtime error). > Actually I would like to suggest an alternative solution for the context > switching of threads based on the availability of I/O. Now this is done by > means of the FIONREAD stuff, but this works only for input. A thread that > is blocked on output (e.g. to a terminal that sends XOFF) will hold up > other threads (if I understand it correctly). It would be more general to > keep track of threads that want to do I/O and in the thread dispatcher test > the runnability of the threads with select(). This has the additional > advantage that it also works for output channels, and I think it works on > more Unixes. The disadvantage is that the various modules are going to be > more intertwined, i.e. it will be more difficult to keep things separated. This is the way it is implemented in our Modula-2+/Ultrix system. The reason I did not go that way at first was the disadvantage you mention; one needs to know about I/O in threads. Given that there can be pieces of programs that do I/O using other mechanisms than readers and writers, I did not find this solution good enough to justify the trouble (and it was big trouble at the beginning, when threads and readers did not work). The position was more like: each call that can block should be protected. This also applies to external libraries such as X; if some call can block, all that is needed is the ability to detect that situation before doing the call. Now that my main argument is no longer as strong (thread and readers/writers seem to work...), we could reconsider that approach. Any suggestions from the rest of the world ? > By the way, I would like single threaded programs not to be bothered with > all the Threads stuff (this also makes them easier to be debugged), but the > above proposal makes that even harder. Are there plans to factor the > Threads stuff out out the kernel? The situation is much like that (may be that is new in 1.5). As long as no Thread.Fork is not called, the thread machinery is not activated; in particular, no signals for context switching. There is still a descriptor for the first thread (that runs your main program) but that thread runs on the original stack. In other words, single-threaded programs look really like C programs. Eric Muller. ------------------------------------------------------------------------------ Date: 12 Jun 1990 1117-PDT (Tuesday) From: Subject: Re: A question and a request I contend that the correct way to approach this problem is to implement the POS interfaces to POSIX that I posted earlier and to then implement a portable POSFileStream on top of it. The POS interfaces give its clients a Modula 3 style interface to POSIX including thread synchronous I/O facilities. Because select(2) is not part of POSIX, the implementation of the thread package and POS will not be portable, but if one is careful I think one should be able to get an implementation that will work with only minor perturbations on Berkeley, Mach or System VR4 based systems. [Note that on Mach based systems one really wants to use the native Mach threads, however I am not sure what facilities that Mach currently provides for thread synchronous I/O to file descriptors.] The Modula 2+/Ultrix threads package uses select and goes to some pains to implement priorities. To do this and avoid polling, it uses nonblocking I/O, select and asynchronous I/O (SIGIO). However it doesn't use nonblocking I/O on terminals because if the program dies without resetting the terminal state, not an uncommon occurrence when writing a new program, many versions of the standard shells get very confused. Garret Swart ------------------------------------------------------------------------------ Date: 12 Jun 1990 1722-PDT (Tuesday) From: Subject: Re: How does one hide default methods? > So am I missing something obvious? Anyone have any ideas on a better way to > deal with this problem? We really should have produced a "Rationale" document. This issue was discussed at some length, with a proposal for some syntactic sugar like PUBLIC(T) that could be used as the supertype of the object used in the concrete revelation (with the obvious expansion from the declaration in the interface). Anyway the committee decided that it did not merit language support. I routinely use the following stylistic convention: TYPE T_public = OBJECT ... END; T <: T_public; REVEAL T = T_public OBJECT ... END; Mick Jordan ------------------------------------------------------------------------------ Date: Wed, 13 Jun 90 12:34:15 met From: Piet van Oostrum The compiler core dumped on a very simple typo I made in Ultrix.i3. See below. % m3 -C UltrixFileStream.m3 File /usr/staff/lib/m3/Ultrix.i3, line 1054: syntax error: missing 'END' File /usr/staff/lib/m3/Ultrix.i3, line 1055: syntax error: missing '=' or '<:' File /usr/staff/lib/m3/Ultrix.i3, line 1064: syntax error: missing ';' File /usr/staff/lib/m3/Ultrix.i3, line 1064: syntax error: missing identifier File /usr/staff/lib/m3/Ultrix.i3, line 1067: Initial module name doesn't match final name (stat) File /usr/staff/lib/m3/Ultrix.i3, line 1067: syntax error: missing '.' runtime error: value out of range CORE DUMPED: m3: /usr/staff/lib/m3/m3compiler terminated, signal=10 Here follows the relevant part of Ultrix.i3 (struct stat changed to conform to HP/UX): TYPE struct_stat = RECORD st_dev : dev_t; st_ino : ino_t; st_mode : u_short; st_nlink : short; st_res1 : short; st_res2 : short; st_rdev : dev_t; st_size : off_t; st_atime : time_t; st_spare1 : int; st_mtime : time_t; st_spare2 : int; st_ctime : time_t; st_spare3 : int; st_blksize: long; st_blocks : long; st_spare4a: long; (* pad acl remote *) st_netdev : dev_t; st_netino : ino_t; st_cnode : u_short; st_rcnode : u-short; <======line 1054======== - should be _ st_netsite: u_short; st_fstype : short; st_realdev: dev_t; st_basemode: short; st_spareshort: short; st_uid : long; st_gid : long; st_spare41: long; st_spare42: long; st_spare43: long; END; Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Wed, 13 Jun 90 15:29:36 met From: Piet van Oostrum Subject: compiler core dump (correction) Ignore my previous message. The problem seems to be something else. I have not yet found what, but it appears to have to do with the operating systems interface on HP/UX. Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Wed, 13 Jun 90 18:01:12 met From: Piet van Oostrum Subject: problem with declaration The following declaration: Timeout:= Ultrix.struct_timeval {0,0}; gave an error in our C compiler: aggregate initialization of auto variable not allowed (or some permutation of these words) Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Wed, 13 Jun 90 17:46:20 met From: Piet van Oostrum Subject: Re: A question and a request On 11 Jun 90 Eric Muller wrote: > Actually read: > > - returns 0 on EOF > - returns -1 and sets errno to EWOULDBLOCK if nothing is present > and O_NDELAY or O_NONBLOCK is set > - blocks otherwise > > If your version of read does the same thing, one solution is say that > 'ioctl' sometime lies and to try the read with O_NDELAY if ioctl sets > n to 0. Then either read returns 0 and ioctl was wrong or read returns > -1 and ioctl was right. > I tried this on HP/UX and it does not work. O_NDELAY is documented to cause read to return 0 if there is no input. O_NONBLOCK is documented to return -1 with errno = EAGAIN (not EWOULDBLOCK) if there is no input. I don't have the POSIX specs, so I don't know if this is in agreement with some standard. Now comes the real problem: The read with O_NONBLOCK gives -1 with EAGAIN regardless whether there is a ^D or no input at all. So this cannot be used to detect the EOF. This leaves the select as the only possibility. Fortunately this discriminates between EOF and no input. The only nuisance with this is that potentially the masks to be provided may be multiword, depending on the maximum number of open file descriptors allowed by the system. The definition of select in Ultrix.i3 specifies VAR int parameters. Maybe these should be changed to ARRAY OF int ? Or even ARRAY OF Word.T? Or SET OF some range? Or must I do some unsafe type conversion? For the time being I will live with a maximum of 32 file descriptors. As long > as no Thread.Fork is not called, the thread machinery is not > activated; in particular, no signals for context switching. There is still > a descriptor for the first thread (that runs your main program) but that > thread runs on the original stack. In other words, single-threaded programs > look really like C programs. > Does this also mean that the code is not loaded? This would make the programs a lot smaller, I think. Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Wed, 13 Jun 90 17:46:20 met From: Piet van Oostrum Subject: Re: A question and a request On 11 Jun 90 Eric Muller wrote: > Actually read: > > - returns 0 on EOF > - returns -1 and sets errno to EWOULDBLOCK if nothing is present > and O_NDELAY or O_NONBLOCK is set > - blocks otherwise > > If your version of read does the same thing, one solution is say that > 'ioctl' sometime lies and to try the read with O_NDELAY if ioctl sets > n to 0. Then either read returns 0 and ioctl was wrong or read returns > -1 and ioctl was right. > I tried this on HP/UX and it does not work. O_NDELAY is documented to cause read to return 0 if there is no input. O_NONBLOCK is documented to return -1 with errno = EAGAIN (not EWOULDBLOCK) if there is no input. I don't have the POSIX specs, so I don't know if this is in agreement with some standard. Now comes the real problem: The read with O_NONBLOCK gives -1 with EAGAIN regardless whether there is a ^D or no input at all. So this cannot be used to detect the EOF. This leaves the select as the only possibility. Fortunately this discriminates between EOF and no input. The only nuisance with this is that potentially the masks to be provided may be multiword, depending on the maximum number of open file descriptors allowed by the system. The definition of select in Ultrix.i3 specifies VAR int parameters. Maybe these should be changed to ARRAY OF int ? Or even ARRAY OF Word.T? Or SET OF some range? Or must I do some unsafe type conversion? For the time being I will live with a maximum of 32 file descriptors. As long > as no Thread.Fork is not called, the thread machinery is not > activated; in particular, no signals for context switching. There is still > a descriptor for the first thread (that runs your main program) but that > thread runs on the original stack. In other words, single-threaded programs > look really like C programs. > Does this also mean that the code is not loaded? This would make the programs a lot smaller, I think. Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Fri, 15 Jun 90 03:38:14 met From: Piet van Oostrum Subject: A few questions about m3make I have the compiler now running on HP/UX. The I/O I have solved with select instead of ioctl(FIONREAD). The compiler has compiled itself (m3compiler.host) and I am going to run the testsuite. I had a problem as the make variable M3BOOTSTRAP is not defined (It is defined in a file Imake.dev-install that is not included in the Imakefile). Now I want to recompile all modules. The porting info says that m3make CFILES would generate all new .[im]c files, but it doesn't because these file are considered up to date. How do I do this? Just delete all .[im]c files or touch all .[im]3 files? Should the target CFILES not do this itself? Another problem that I had was when I changed some of the interface files (like Ultrix.i3), I had to find out manually which files to recompile. Is there a mechanism planned to make Imakefiles from the IMPORT specs? How do the developers do this themselves? Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: 15 Jun 1990 0942-PDT (Friday) From: Subject: Subtype field masking My bad experiences are perhaps related to the weak syntax that Modula-3 has for specifying method overrides. I.e. the similarity between METHOD p() := P END METHOD p := P END makes it very easy to accidently redeclare a method instead of overriding it. Perhaps a keyword would help avoid this trap. I cant see any use for masking when the supertype field is explicitly visible; since every visible field in an object type presumably has some meaning for a client (else why isnt it hidden). So the usefulness of the feature is only to resolve ambiguity between new "friends" of concrete revelations. I tend to hold the view that (a) it is a bad idea to look inside other abstractions (if something is useful it should be exported - perhaps through a secondary interface) and (b) that this happens infrequently enough that one or other of the fields could be renamed. A final argument against masking is the fact that it is this facilty that forces the strict compile time subtype ordering of revelations, in order to properly disambiguate repeated fields. Mick ------------------------------------------------------------------------------ Date: Fri, 15 Jun 90 17:16:08 EDT From: moss%ibis@cs.umass.edu (Eliot Moss) Subject: Unsubscription requests Since my mail box has been getting clogged with these, I would like to remind folks of a long standing Internet tradition: for a mailing named "foo@site" the standard protocol for mailing list update queries and requests (addition, deletion, etc.) is to send mail to "foo-request@site". This avoids annoying everyone on the list, wasting resources telling them something they don't want to hear. Sending to foo-request (in this case m3-request@src.dec.com) is also more likely to have prompt effect on the mailing list maintainer. Thanks! J. Eliot B. Moss, Assistant Professor Department of Computer and Information Science Lederle Graduate Research Center University of Massachusetts Amherst, MA 01003 (413) 545-4206; Moss@cs.umass.edu ------------------------------------------------------------------------------ Date: Fri, 15 Jun 90 15:00:31 PDT From: Eric Muller Subject: Re: WITH statement core dump > An apparent bug in version 1.4 on a SPARC: referencing a non-procedure > when defining an alias in a WITH statement produces a NIL pointer > reference in the compiler. Fixed for the next release. Thanks. Eric Muller ------------------------------------------------------------------------------ Date: Fri, 15 Jun 90 14:58:23 PDT From: Eric Muller Subject: Re: A few questions about m3make > I have the compiler now running on HP/UX. The I/O I have solved with select > instead of ioctl(FIONREAD). > The compiler has compiled itself (m3compiler.host) and I am going to run > the testsuite. Great ! > I had a problem as the make variable M3BOOTSTRAP is not defined (It is > defined in a file Imake.dev-install that is not included in the Imakefile). I don't remember the details of 1.4, but Imake.dev-install is one of those files that serve as a template for another one (such as Imake.install ?) > Now I want to recompile all modules. The porting info says that m3make > CFILES would generate all new .[im]c files, but it doesn't because these > file are considered up to date. How do I do this? Just delete all .[im]c > files or touch all .[im]3 files? Should the target CFILES not do this itself? Just deleting the files is ok. The problem is that the distribution is geared towards installing the system which makes it a bit inconvenient for developping. Having the CFILES target deleting the files first would be inconvenient for development, in cases where there the compiler generates illegal C; you want to fix the compiler and restart on this same file. > Another problem that I had was when I changed some of the interface files > (like Ultrix.i3), I had to find out manually which files to recompile. Is > there a mechanism planned to make Imakefiles from the IMPORT specs? How do > the developers do this themselves? Well, there is this thing called m3imc that is supposed to do the job, looking at version stamps. But I don't use is myself, I don't think it works well with make (make assumes that if it has to rebuild a target, then the target has changed after this rebuilding and therefore the other things that depend on it have to be rebuild). Mick Jordan announced on the m3 list that he has a Makefile generator, but I don't use it either. Eric Muller. ------------------------------------------------------------------------------ Date: 15 Jun 1990 1549-PDT (Friday) From: Subject: Re: A few questions about m3make > > Another problem that I had was when I changed some of the interface files > > (like Ultrix.i3), I had to find out manually which files to recompile. Is > > there a mechanism planned to make Imakefiles from the IMPORT specs? How do > > the developers do this themselves? > > Well, there is this thing called m3imc that is supposed to do the job, > looking at version stamps. But I don't use is myself, I don't think it > works well with make (make assumes that if it has to rebuild a target, > then the target has changed after this rebuilding and therefore the > other things that depend on it have to be rebuild). Mick Jordan > announced on the m3 list that he has a Makefile generator, but I don't > use it either. > > Eric Muller. > As I read this, I am in the process of bringing up the makefile generator at SRC. It depends on the Olivetti system, however. As of now, Olivetti has agreed to make the system available without a licence for non-commercial use, (and we are leaning on them to remove the non-commercial restriction). My plan is to try to get this and as many of the other Olivetti tools running under the SRC system, and then include them in the distribution. Mick Jordan ------------------------------------------------------------------------------ Date: Sat, 16 Jun 90 15:16:38 EDT From: moss%ibis@cs.umass.edu (Eliot Moss) Subject: Some Report questions/problems A colleague was visiting me recently and pointed out some potential difficulties with the revised report that I would like to pass on now: 1) On page 15 where the ARRAY subtyping rule is introduced, we believe the words are correct, but the notation has a small ambiguity. Consider this form (superscripts indicated by ^ and subscripts by _, as in TeX/LaTeX): (ARRAY OF)^m ARRAY J_1, ..., J_n OF ARRAY K_1, ..., K_p OF T The problem is that does this mean when n or p is zero, or both, for that matter? If an "ARRAY OF" is left behind, it would appear to increment m, which is wrong. This is really a minor notational matter but should perhaps be cleared up since the notation was not described precisely enough. 2) On page 31: It is stated that there can be at most one definitive revelation, but it is *not* said within what scope. Is it a module/interface, or the whole program? What if there are multiple revelations and they are identical? (If they are not identical it is clearly an error.) I think I know the answer (the scope is the program so multiple definitive revelations are disallowed even if they are the same) but the report is not clear on this point. 3) On page 34, the concept of a *program* is defined. One question arises immediately: are *interfaces* part of programs? (The definition mentions only modules.) It is clear that interfaces are involved somehow. Another question is can two modules of the same program have the same name? A related question is how about two interfaces? My interpretation of the intent behind the report is that any particular implementation of Modula-3 has a way of mapping module and interface names (and perhaps program names, if there is such a thing) to unique files in a given context, so there cannot be duplicate module or interface names. But, the idea of mapping to files does not preclude the name within the contents of the file from being different from the name that caused the file to be located and used, etc. In short, there seems to be an implicit compilation or at least module aggregation (program formation) model that may need to be more explicit to clarify these issues. Yours in precision :-) Eliot J. Eliot B. Moss, Assistant Professor Department of Computer and Information Science Lederle Graduate Research Center University of Massachusetts Amherst, MA 01003 (413) 545-4206; Moss@cs.umass.edu ------------------------------------------------------------------------------ Date: 16 Jun 1990 1450-PDT (Saturday) From: Subject: Re: Some Report questions/problems Eliot Moss writes: 1) On page 15 where the ARRAY subtyping rule is introduced, we believe the words are correct, but the notation has a small ambiguity. Consider this form (superscripts indicated by ^ and subscripts by _, as in TeX/LaTeX): (ARRAY OF)^m ARRAY J_1, ..., J_n OF ARRAY K_1, ..., K_p OF T The problem is that does this mean when n or p is zero, or both, for that matter? If an "ARRAY OF" is left behind, it would appear to increment m, which is wrong. This is really a minor notational matter but should perhaps be cleared up since the notation was not described precisely enough. We'll try to choose new words that avoid any possible misinterpretation. 2) On page 31: It is stated that there can be at most one definitive revelation, but it is *not* said within what scope. Is it a module/interface, or the whole program? What if there are multiple revelations and they are identical? (If they are not identical it is clearly an error.) I think I know the answer (the scope is the program so multiple definitive revelations are disallowed even if they are the same) but the report is not clear on this point. We'll make it clear that it is a *program* that can't more than one definitive revelation for the same type. 3) On page 34, the concept of a *program* is defined. One question arises immediately: are *interfaces* part of programs? (The definition mentions only modules.) It is clear that interfaces are involved somehow. Another question is can two modules of the same program have the same name? A related question is how about two interfaces? My interpretation of the intent behind the report is that any particular implementation of Modula-3 has a way of mapping module and interface names (and perhaps program names, if there is such a thing) to unique files in a given context, so there cannot be duplicate module or interface names. But, the idea of mapping to files does not preclude the name within the contents of the file from being different from the name that caused the file to be located and used, etc. In short, there seems to be an implicit compilation or at least module aggregation (program formation) model that may need to be more explicit to clarify these issues. Oops. I guess a program should have been defined as a collection of modules and interfaces that includes every interface imported or exported by any of the modules or imported by any of the interfaces. As for naming, I think the report should leave all issues of file names and search paths to the implementation, and concern itself only with the names that appear in the source text for modules and interfaces. The language explicitly allows the same name to denote both an interface and a module; I would be inclined for forbid all other name conflicts. (There is no semantic necessity to prohibit several modules from having the same name, but because of practical considerations, like debugging, I'm not worried that anybody will complain if we prohibit this.) ------------------------------------------------------------------------------ Date: Sat, 16 Jun 90 18:01:07 EDT From: moss%ibis@cs.umass.edu (Eliot Moss) Subject: Re: Some Report questions/problems Just to follow up to Greg's response to my recent posting: > .... As for naming, I think the report should leave all issues > of file names and search paths to the implementation, and concern > itself only with the names that appear in the source text for modules > and interfaces. I agree with this approach; defining a program as including any interface directly or transitively imported sounds good. As for name conflicts, I would suggest that while it is clearly reasonable for a module and an interface to have the same name, and I agree with Greg that it is probably semantically sound to allow modules to have the same name yet reasonable to prohibit it anyway, I think it is semantically incorrect to allow two interfaces to have the same name, since interface names can be used in designators, etc. (This is probably obvious to everyone on the list.) Cheers .... Eliot J. Eliot B. Moss, Assistant Professor Department of Computer and Information Science Lederle Graduate Research Center University of Massachusetts Amherst, MA 01003 (413) 545-4206; Moss@cs.umass.edu ------------------------------------------------------------------------------ Date: Sun, 17 Jun 90 19:55:52 met From: Piet van Oostrum Subject: consolidate_to_object Most Imakefiles contain a consoldate_to_object, which - as it stands in Imake.definitions - links all objects in the directory in a single object module. To me this seems a waste as it decreases the selective loading of object. I got especially stuck at the X directory, where the resulting huge object is put in an archive and I get a string table overflow from ar (on HP/UX). It seems to be much more logical to put the individual object files in a library, so that you only load what you need. Why is this done? And will it be the same in 1.5? Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Mon, 18 Jun 90 11:26:34 PDT From: Eric Muller Subject: Re: consolidate_to_object > Most Imakefiles contain a consoldate_to_object, which - as it stands in > Imake.definitions - links all objects in the directory in a single object > module. To me this seems a waste as it decreases the selective loading of > object. I got especially stuck at the X directory, where the resulting huge > object is put in an archive and I get a string table overflow from ar (on > HP/UX). It seems to be much more logical to put the individual object files > in a library, so that you only load what you need. Why is this done? And > will it be the same in 1.5? 1.5 introduces multiple libraries, without consolidation of the objects. Eric Muller. ------------------------------------------------------------------------------ Date: Tue, 19 Jun 90 00:04:30 met From: Piet van Oostrum Subject: stderr The tests p039 and p041 don't close Stdio.stderr. So when these are run by imake (with stderr redirected to a file) the output will not be flushed. I think stderr should be unbuffered. Or at least it should be closed automatically (this might be hard without finalizers). There are also some fprintf(stderr) calls in some C routines. This may give strange interactions. Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: 19 Jun 1990 0916-PDT (Tuesday) From: Subject: mailing list protocol Please, please send meta-messages like "please unsubscribe me" and "change my address from x to y" to m3-request@src.dec.com. Messages sent to m3@src.dec.com reach over 200 people around the globe and none of them can do anything to satisfy your request. Messages sent to m3-request reach 3 people here at SRC who will do their best to satisfy your request. - Bill Kalsow ------------------------------------------------------------------------------ Date: Tue, 19 Jun 90 14:57:17 PDT From: Eric Muller Subject: m3 flags, M3DEFPATH and M3OBJPATH Following messages I received from Mick Jordan, I went back to read the m3 manual page and it is indeed confusing. Here are the current rules to find the imported interfaces: 1. the interface I is in the file I.i3; this file is searched into a list of directories, called the search path. 2. if there are no -D options in the command line and the M3DEFPATH environment variable is not set, the search path is that which has been decided at the time Modula-3 was installed; the standard configuration is .: 3. if there are no -D options in the command line and the M3DEFPATH environment variable is set, the search path is the value of that environment variable. 4. if there are some -D options then, regardless of the status of M3DEFPATH: - initially, the search path is set to value described in 2 above. - processing the -D options from left to right: - a "-D" option adds in front of the search path - a "-D" option resets the path to . I propose to remove the M3DEFPATH environment variable, and change these rules to: 1. as above 2. initially, the search path is set to some builtin value (as in 2 above). Then, processing the -D options from left to right: - a "-D" adds in front of the search path - a "-D" option resets the path to be empty The same applies, mutatis mutandis, to -D and M3OBJPATH. In addition -lm3misc should be enough to properly link with the Modula-3 misc library (m3 should just give a -L path to the loader). So that users don't need to know where are the standard libraries, I propose the introduction of the m3make command. This command is similar to make, but: - it uses a file called Imakefile instead of Makefile or makefile - one needs to have the line #include "m3.tmpl" near the beginning - the m3.tmpl file defines the following symbols I3_CORE I3_DATA I3_BASICS I3_IO I3_MATH I3_MISC I3_ULTRIX_3_1 I3_AIX_3_1 I3_AIX_PS2_1_2 I3_IBM_4_3 I3_POSIX I3_UNIX I3_XLIB I3_XT I3_XAW M3_UNIX if $(I3_FOO) is one of the options passed to m3, then the directory that contains the interfaces implemented by foo are included in the search path. Typically, I3_FOO is "-D/usr/local/lib/m3/foo"; the proper value is determined during the installation of SRC Modula-3. I3_UNIX has the same value as one of I3_ULTRIX_3_1, ... according to machine on which m3make is run. M3_UNIX is the same as one of -lm3ultrix-3.1, ... according to the machine. - other predefined symbols: M3 the installed m3 M3FLAGS empty - there are some additional suffix rules .m3.mo: $(M3) $(M3FLAGS) -c $< .i3.io: $(M3) $(M3FLAGS) -c $< A typical Imakefile for a simple module that needs the data and misc libraries looks like: #include "m3.tmpl" M3FLAGS = $(I3_DATA) $(I3_MISC) pgm: a.mo $(M3) a.mo -lm3data -lm3misc The astute reader will notice that "m3make" is no more than "imake -I" with a trivial (non Modula-3) template. The motivation for requiring the #include "m3.tmpl" rather than have this line in the template is that the X templates can be used. That is, the Imakefile above can be given to m3make or to the X imake. As SRC Modula-3 provides a complete set of interfaces to X11R4, this should make the development of Modula-3/X programs easier. Future releases of SRC Modula-3 could introduce new rules, if those provided by the standard X templates are considered inadequate. Comments and suggestions are welcome, Eric Muller. ------------------------------------------------------------------------------ Date: Wed, 20 Jun 90 16:00:43 -0500 (CDT) From: Dick Orgass Subject: Re: m3 flags, M3DEFPATH and M3OBJPATH Many thanks to Eric Muller for clarifying the behavior of the m3 command. Removing the use of the path environment variables makes builds much more reliable because all of the path information is in the make files and I think it's a clear improvement. [It turns out that our Modula-3 view for ez (from the Andrew Toolkit) uses M3DEFPATH to look for the sources to library interfaces to enable a quick display of interfaces so we would probably keep the variable around for this reason. A possible bad side effect is that one might not be looking at the same source that the compiler sees.] Eric's proposal that imake be the principal tool for building Modula-3 programs causes me considerable concern. The X Consortium use of imake includes using makedepend. As a result, the C dependencies are added to the imake generated make files. This is not the case for the SRC system as "shipped". Let me use the Version 1.4 system as an example. Suppose that one changes a library interface for some reason. (The Ultrix* interfaces are a good example.) It's not enough to simply run m3make to create a new .io and to redo the link. To get the system built correctly, one has two choices: (1) Delete the entire set of object files and rebuild everything. (2) Walk the source tree with fgrep to look for clients of the changed interface and then delete the corresponding .[im]o files. Properly constructed make files would simply rebuild the changed interface and all clients. In contrast, when one builds the Olivetti Modula-3 system, one simply changes a source file and types make. All of the affected files are recompiled and the link is done. This is the way it should work. The Olivetti system was built using Mick Jordan's m3dep program which generates all of the dependencies in the make files. One can even add a client of some interface, run m3dep in the directory and rebuild. Again as it should be. I would agree with using imake in place of make and a program like m3dep only if the imake generated make files have Modula-3 dependency information like that provided for C by makedepend. Is it worth the effort to write a Modula-3 version of makedepend given that m3dep is already working for SRC Modula-3? Generating make files with computed dependency information takes long enough that it then becomes useful to keep the generated make files; this is how the X builds are done. (The X Consortium generated make files will rebuild the make file from the imake file if the imake file is newer than the make file.) I've been thinking about how to use the conditionals and string processing functions of GNU make to do essentially all that is currently being done by imake in the Modula-3 distribution. It appears that it should work but I haven't had time to put it into practice so my statement is only an opinion at the moment. Dick ------------------------------------------------------------------------------ Date: 20 Jun 1990 1507-PDT (Wednesday) From: Subject: Fixing EXTERNAL This is a proposal to fix the EXTERNAL mechanism in a way that is upwards compatible with the SRC compiler. The SRC compiler doesnt implement what the report suggests, in particular it provides no mechanism for specifying the language that the procedure (etc) is implemented in. It also doesnt accept EXTERNAL on an interface as shorthand for application to all declarations in the interface. Unfortunately, the renaming mechanism used by SRC m3 is in direct conflict with the recommendation in the report for indication the language. I.e. <* EXTERNAL L *> means "implemented in language L" in the report, but "named L" in SRC M3. Since there is a lot of code already in existence (e.g. all the X interfaces) which uses the SRC M3 convention, I think we have to accept it as a de facto standard and find another way to incorporate language information. The Olivetti compiler used a postfix scheme for renaming, e.g. "C:name", so the obvious solution for SRC M3 is to use a prefix scheme, e.g. "name:C". So the syntax would be: <* EXTERNAL [ [rename] [":" L] ] *> Note that the language specifier is optional, the interpretation being that the implementation defines some default language, e.g. C. I am indifferent as to whether the entire argument can be quoted like a text-literal. It seems unnecessary, but the current compilers allow it. Examples: 1. <* EXTERNAL *> PROCEDURE P; A procedure implemented in the default language, named according to the "obvious" mapping of 'P' into that language. 2. <* EXTERNAL id *> PROCEDURE P; As 1, but exchange 'P' by 'id'. 3. <* EXTERNAL :C *> PROCEDURE P; A procedure implemented in the C language, named according to the "obvious" mapping of 'P' into the C name space. 4. <* EXTERNAL id:C *> PROCEDURE P; As 3, but exchange 'P' by 'id'. I am not especially keen on ":" as a separator; there may be a better choice, e.g. @. Note that the "obvious" mapping is intended to support things like qualified naming in other languages. Also, in the case of a rename, it should be a source-source mapping. Details like whether an underline gets prepended are left to the implementation for portability. I do think that the SRC M3 compiler should support <* EXTERNAL *> INTERFACE ... Comments welcome. Mick Jordan ------------------------------------------------------------------------------ Date: Wed, 20 Jun 90 15:48:18 PDT From: Eric Muller Subject: Re: Fixing EXTERNAL > This is a proposal to fix the EXTERNAL mechanism in a way that is > upwards compatible with the SRC compiler. Just out of curiosity; is there a useful external access that is not possible today because one cannot specify the language ? Eric Muller. ------------------------------------------------------------------------------ Date: Thu, 21 Jun 90 10:01:32 GMT From: patl@Sun.COM (Pat Lashley) Subject: Re: m3 flags, M3DEFPATH and M3OBJPATH ... I would agree with using imake in place of make and a program like m3de p only if the imake generated make files have Modula-3 dependency information like that provided for C by makedepend. Is it worth the effort to write a Modula-3 version of makedepend given that m3dep is already working for SRC Modula-3? ... I agree that a dependancy generator should be part of the distribution; but my personal oppinion is that imake is one of the cruftiest, most rebarbative pieces of over-complex, under functional code to gain wide distribution. I would -strongly- suggest the use of GNU make instead. Using include files for configuration and generated dependancies would greatly simplify and clarify the make process. It may be desirable to write a `gmake' which would take produce a GNUmakefile from an imakefile; but this process would only need to occur when the imakefile changes, not whenever the basic (included) configuration files change. -Pat ------------------------------------------------------------------------------ Date: 21 Jun 90 19:29 +0100 From: "(Mats Weber)" Subject: Re: m3 flags, M3DEFPATH and M3OBJPATH RFC-822-Headers: Received: from elcgl.epfl.ch by SIC.Epfl.CH with VMSmail ; Thu, 21 Jun 90 19:29 :04 N X-ST-Vmsmail-To: ELSIC::GW::"m3@src.dec.com" ================== Why not take the same approach Ada does, that is, every Ada compiler comes with a library manager that can figure out automatically what has to be recompiled and in which order each time a compilation unit is modified, and without requiring the user to write make or Imake files. The dependencies among compilation units are part of the language and so the compiler is in a good position to figure them out. I have been using this kind of feature for a few years now and it is a big improvement over the Modula-2 environment I was using before. Mats ------------------------------------------------------------------------------ Date: 21 Jun 1990 1428-PDT (Thursday) From: Subject: Re: m3 flags, M3DEFPATH and M3OBJPATH > Why not take the same approach Ada does, that is, every Ada compiler comes > with a library manager that can figure out automatically what has to be > recompiled and in which order each time a compilation unit is modified, and > without requiring the user to write make or Imake files. > > The dependencies among compilation units are part of the language and so > the compiler is in a good position to figure them out. > This is what 'm3dep' does. All it requires as input is a set of directories (packages, libraries) to work with. It then figures out the structure of the system and, in this implementation, writes a makefile. It would be no big deal to have it also do the 'make' part, but since 'make' does that pretty well, we havent bothered. I have been using this kind of feature for a few years now and it is a big > improvement over the Modula-2 environment I was using before. > > Mats > Actually, this tool evolved from a Modula-2 version. You can read about that in "Experiences in Confugriation Management for Modula-2", ACM Software Engineering Notes, Vol 17, 7, Nov. 89. Mick Jordan ------------------------------------------------------------------------------ Date: 21 Jun 1990 1435-PDT (Thursday) From: Subject: Re: Fixing EXTERNAL > Just out of curiosity; is there a useful external access that is not possible > today because one cannot specify the language ? > > Eric Muller. I can see C++ as an important language to interface to. I dont want to have to understand the name transformations performed by a typical C++ compiler. I.e. I want to use the C++ name and have the M3 compiler do the mapping. I also want to be able to describe EXTERNAL types, assuming the compiler is prepared to work with them, e.g. C arrays, rather than just hope that I can pun them in Modula-3. In general, I dont think that you can assume that the common calling conventions of the VAX/DECstation are a complete solution to this problem. Mick Jordan ------------------------------------------------------------------------------ Date: Thu, 21 Jun 90 23:07:19 +0200 From: Frode Odegard Subject: Re: m3 flags, M3DEFPATH and M3OBJPATH Mats Weber writes: > Why not take the same approach Ada does, that is, every Ada compiler comes > with a library manager that can figure out automatically what has to be > recompiled and in which order each time a compilation unit is modified, and > without requiring the user to write make or Imake files. Q: Is this really specified in the standard ? Does an implementation really have to figure out dependencies itself ? - Frode ------------------------------------------------------------------------------ Date: Fri, 22 Jun 90 10:45:44 EDT From: dcc@ATHENA.MIT.EDU Subject: My initial use of Modula-3. Hello, I work for DEC at MIT/Project Athena. I've been silently watching the discussions about Modula-3, but I haven't until recently been able to install the 1.4 release here at MIT/Project Athena. I'm interested in Modula, but haven't had any great experience with it. My goal with Modula is to gain experience with it and make it available to folks here to experiment with, though I somewhat doubt that Modula will catch on in a big way here, as folks at Athena are quite enamored with C and C++. Anyway, I just got the kit built and have been trying to use it. I have a few questions I'd like to ask mostly in the tone of getting help with using it with ULTRIX, C, and X. I've been reluctant to clutter the m3 distribution with my questions since the discussion there seems to have moved beyond this level (the trying to get started stage). If there is some other place I should direct my questions please let me know. My current obsticle is trying to use the dbx debugger to debug a simple program that reads input from Stdio.stdin using the Rd.GetLine procedure. When I run the program without dbx the first GetLine requires two carriage returns before the read completes, subsequent GetLine's work properly. When I try to debug the program using dbx, my first call never completes regarless of what input I type (carriage return or ^d). I've tried telling dbx to ignore most signals, but this didn't have any effect on the problem. I should note that I'm using the last field test of ULTRIX 4.0. Is this a problem in general? --David Carver ------------------------------------------------------------------------------ Date: Fri, 22 Jun 90 09:42:09 -0500 (CDT) From: Dick Orgass Subject: Re: m3 flags, M3DEFPATH and M3OBJPATH Some observations about Mats Weber's comments: (1) I think think it's very important that a compiler and library for a language be independent of a specific build process and tools. The language can define things like compile order, etc. but not the mechanics of building and maintaining software. Introducing a new language into an organization is, at best, a very difficult job even when the need for a new language is quite evident to senior management and some of the technical people. If using this new language also requires a complete overhaul in the mechanical details of a development process and tools the task becomes almost impossibly difficult. This is true if the new build process is heavenly compared to the old one. Requiring a specific build process as a requirement for using a language is, in my opinion, a gurantee that use of the the language will be quite limited, particularly in the commercial environment. (2) I'm convinced that a build process that makes a programmer completely unaware of what is linked into an application and of the consequences of a change to an interface without providing an easy way to find out what happened. A simple example: A few years ago, I was working on a small Modula-2 program for PC-DOS using a Modula-3 development product that provided automatic recompilation of everything without any trace of information in console output or in disk files. I made two totally trivial changes to two modules, adding an import of a library module in each case. The size of the resulting executable doubled! This was unacceptable, but the only tool I had to find out what happened was the load map of the executable. Since the two changes depended on each other, I couldn't undo one of them. After that experience, I built a make file and stopped using the automatic recompilation; I was fortunate to have Modula-2 make file builder and one quick look at the make file identified the offensive import. (3) I'm convinced that a reliable build tool is essential for any language and that providing one tool that can accurately rebuild exactly the files that are changed is essential for gaining acceptance of a new language and/or compiler. An example: Just before the Olivetti Modula-3 effort was terminated, we were in the process of deciding between the Olivetti and SRC compilers as our main Modula-3 compiler. Outside of the small group that had looked at the compilers themselves in a bit of detail, the clear view was that the Olivetti system was much better because m3dep came with the system. The larger group insisted that we include the cost of porting m3dep in the cost of SRC Modula-3. We're very happy that Mick is modifying the tool for SRC Modula-3 and are looking forward to using it. Dick ------------------------------------------------------------------------------ Date: 22 Jun 1990 0954-PDT (Friday) From: Subject: m3 -t I think this should be the default. The only time I might want to stop a set of compilations is if the first is an interface on which others depend. E.g. "m3 -c Foo.i3 Foo.m3 Bar.m3". The usual case of compiling several modules and having some not done because of earlier errors is not intuitive. Mick Jordan ------------------------------------------------------------------------------ Date: 22 Jun 1990 1127-PDT (Friday) From: Subject: IMPORT/EXPORTS bug The following code is legal, but faulted by the SRC compiler (1.5). MODULE M EXPORTS I; IMPORT I; END I. Some of us really like qualified naming! Mick Jordan ------------------------------------------------------------------------------ Date: Fri, 22 Jun 90 11:40:54 PDT From: Eric Muller Subject: Re: stderr Piet van Oostrum writes: > The tests p039 and p041 don't close Stdio.stderr. So when these are run by > imake (with stderr redirected to a file) the output will not be flushed. Fixed in 1.5. > I think stderr should be unbuffered. Or at least it should be closed > automatically (this might be hard without finalizers). There are also some > fprintf(stderr) calls in some C routines. This may give strange > interactions. There has been a discussion on the subject some time ago. Greg Nelson (among others) argued that there should be a runtime mechanism to register procedures to be executed at the end of the program execution; I feel that there should be language construct similar to the module bodies. Stdio now has a Close procedure that does the right thing on stdin/stdout/stderr. Of course, if a program dumps core while stderr is buffered, part of the output may be lost. Eric Muller. ------------------------------------------------------------------------------ Date: Fri, 22 Jun 90 20:36:46 +0200 From: Frode Odegard Subject: Modula dcc@ATHENA.MIT.EDU writes: > I'm interested in Modula, but haven't had any great experience with it. > My goal with Modula is to gain experience with it and make it available to > experiment with, ... It probably should be pointed out that a lot of people will think of Modula-2 if you just say "Modula". :-) - Frode (Modula-2 hacker) ------------------------------------------------------------------------------ Date: Fri, 22 Jun 90 14:46:08 PDT From: Eric Muller Subject: Re: My initial use of Modula-3. David Carver writes: > I have a few > questions I'd like to ask mostly in the tone of getting help with using it > with ULTRIX, C, and X. I've been reluctant to clutter the m3 distribution > with my questions since the discussion there seems to have moved beyond this > level (the trying to get started stage). If there is some other place I > should direct my questions please let me know. The m3 mailing list is the right place to discuss any problem related to SRC Modula-3 and (de facto) to Modula-3 more generally. If you have trouble with the implementation or the language, it is quite likely that others have the same problems and they can benefit from the discussion. Go ahead, we'll try to give good answers to your questions. > My current obsticle is trying to use the dbx debugger to debug a simple > program that reads input from Stdio.stdin using the Rd.GetLine procedure. > When I run the program without dbx the first GetLine requires two carriage > returns before the read completes, subsequent GetLine's work properly. Rd.GetLine is broken in 1.4. I hope the version in 1.5 is ok. ------------------------------------------------------------------------------ Date: Fri, 29 Jun 90 12:35:34 met From: Piet van Oostrum Subject: Re: Port of m3-1.5 to HP/UX The compiler crashes with a range fault if a record is declared with a duplicate field and at least one field following that. INTERFACE Test; TYPE test = RECORD a: INTEGER; a: INTEGER; b: INTEGER; END; END Test. It occurs in this piece of code from RecordType.Bind: FOR i := 0 TO nFields - 1 DO o := objs[i]; Field.SplitX (o, j, type, default); p.fields[j] := o; (**** HERE *) types[j] := NamedType.Strip (type); END; The j value appears to be bigger than the upper bound of p.fields Piet* van Oostrum, Dept of Computer Science, Utrecht University, Padualaan 14, P.O. Box 80.089, 3508 TB Utrecht, The Netherlands. Telephone: +31-30-531806 Uucp: uunet!mcsun!ruuinf!piet Telefax: +31-30-513791 Internet: piet@cs.ruu.nl (*`Pete') ------------------------------------------------------------------------------ Date: Fri, 29 Jun 90 13:08:16 -0400 From: Roger Hoover Subject: In the 1.5-beta release, M3Machine.h.SPARC (as well as the other machine versions) have volatile char nil_check_char; on line 36. The SPARC compiler does not know what 'volatile' is and there is no macro definition for it. What is this supposed to do? roger hoover rhoover@ibm.com ------------------------------------------------------------------------------ Date: Fri, 29 Jun 90 12:10:43 PDT From: Eric Muller Subject: Re: volatile > In the 1.5-beta release, M3Machine.h.SPARC (as well as the other > machine versions) have > volatile char nil_check_char; > on line 36. The SPARC compiler does not know what 'volatile' is > and there is no macro definition for it. What is this supposed > to do? Here is how we do NIL checking at run-time. In all cases, we have a REF that will be dereferenced, possibly with an offset (for example in r^[34]). On some machines, low addresses are not mapped; thus there is no need to check that r # NIL, as "34(r)" will try to access a non-mapped memory location and the operating system will generate the error. But not all machines are like that and even on such machines, the offset can be large enough that even with r = NIL, "offset(r)" will be a mapped address. There is also the situation in which the compiler cannot determine if the offset is small or large: objects that have an opaque supertype. In those cases, we build a address that is mapped for non-NIL refs but unmapped for NIL refs, namely ((int *) r)[-1]. This address is mapped for non-nil refs because a dynamically allocated piece of data is preceded by its typecode. 0xffffffff is not mapped on most machines. Then we try to derefence that address. However, writing "x = ((int *) r)[-1];" does not make any good if x is not used; an optimizing compiler has no trouble figuring that and simply ignores this line. Declaring "x" volatile basically means that assignments to "x" must be performed regardless of what the compiler thinks. One of the main uses of that storage class is for device registers, where the consumer is outside of the scope of the compiler. volatile is ANSI-C and it is painfull to write system code if you have an optimizing compiler but no volatile. The compiler generates calls to the NILCHECKA macro when it is known that the offset is small (actually < 1024) and calls to NICHECKB for (potentially) large offsets. Then each machine "decides" (via M3Machine.h) what to do in each case. You may wonder why the small/large limit is not one the values defined by Target.i3; right now, there are very few differences between the C files generated from the compiler itself and this allows us to make a single distribution that is not much larger than a specialized distribution. If this limit parameter was in Target.i3, almost every C file would have at least two instances (say the VAX - 0 mapped - and the DECstation - 0 unmapped). Furthermore, Eric Muller. ------------------------------------------------------------------------------ Date: 29 Jun 90 12:50:38 PDT (Friday) From: "James_Plank.PARC"@Xerox.COM Subject: Re: Hi Roger -- I just installed the 1.5 beta version on the SPARC's here, and I don't know if any of the bug reports have been forwarded. In any case, I'm including all the bugs I found (along with the "volatile" one you mentioned) below: Differences from the manual 1: tar: tar will not work from a Sun to a MIPS file system (e.g. trying to do a tar on a file on /net/gharlane while logged into the sun "ptarmigan" will gag) -- see ~demers/tar/tar* for a fixed version which will do the above. 2: /util/config.dist-SPARC needs some changes: (here the correct version is in config): diff config.dist-SPARC config 19c19,20 < PREFIX = /usr/local --- > /* PREFIX = /usr/local*/ > PREFIX = /usr/m3-1.5 45,49c46,50 < LIBX11 = /usr/local/lib/libX11.a < LIBXT = /usr/local/lib/libXt.a < LIBXMU = /usr/local/lib/libXmu.a < LIBXEXT = /usr/local/lib/libXext.a < LIBXAW = /usr/local/lib/libXaw.a --- > LIBX11 = /import/X11R4/usr/lib/libX11.a > LIBXT = /import/X11R4/usr/lib/libXt.a > LIBXMU = /import/X11R4/usr/lib/libXmu.a > LIBXEXT = /import/X11R4/usr/lib/libXext.a > LIBXAW = /import/X11R4/usr/lib/libXaw.a 57c58 < DVIPS = dvips --- > DVIPS = dvi2ps 2a: If in util/config, you change COPT to -g and M3OPT to -X1g, then it will make things easier to debug. It you leave them, you'll get the optimized version, which is also broken. 3: In /util/clone.c, change gd_name to d_name 4: In /system/runtime/M3Machine.h.SPARC, get rid of the keyword volatile, since the sun C compiler isn't ANSII (or else try using the gnu c compiler) 5: The compiler craps out upon trying: .ROOT/system/driver/m3.local -D../Interfaces -X1g -c AutoFlushWr.i3. To fix this, add "exit(0);" to the end of main, at the end of the file system/runtime/M3Runtime.c. 6: Lines 753 and 759 of system/driver/m3.c both say: args = Prepend (args, O.pass[1]); Remove them. They put an extra /bin/cc into the argument list, which is already there from the "args = Append (args, O.pass[1]);" call on line 741. 6a: The procedure LinkProgram() in m3.c doesn't pass its options as defined by the -X command line option. Fix this by putting args = AppendList (args, O.passArgs[2]); between the lines: args = Prepend (args, "-o"); and if (O.gFlag) args = Append (args, "-g"); Also, where is the -p option of m3ld documented? Anywhere? 7: The variables FBLKINUSE and FBLKANDSET aren't defined in /libs/aix-ps2-1.2/os/Unix.i3. If you know correct values, define them, otherwise don't make the library, as it probably won't be usec on a SPARC anyway: Comment out everything in /libs/aix-ps2-1.2/Imakefile. 8: As well as having FBLKINUSE and FBLKANDSET undefined, /libs/aix-3.1/os/Unix.i3 is missing semi-colons from lines 41 to 43. Either fix it, or comment out /libs/aix-3.1/Imakefile. 9. You might have to make some directories by hand if the make install bombs. Make install copies the new files and changes the owner to root, which may not be altogether desirable (especially as the ranlib calls to newly copied libraries crash as a result of not having root permission). A separate makefile might be worthwhile. Jim Plank ------------------------------------------------------------------------------