A proposal for Modula-3 language changes to support an integer type
larger than the native size of the target processor.

This proposal satisfies (I believe) the following principles:

The static correctness and type analysis of existing code written to
the current language definition will not change with the new
definition.  This also implies that the new language definition will
not introduce new type mismatches that would make existing pickles
unreadable.

The runtime semantics and time and space efficiency of existing code
written to the current language definition will also not change with
the new definition, if the native word size of the implementation does
not change.  Of course, porting existing code to an implementation
with different native word size can change the runtime semantics by
changing the supported value range, with or without language change.

The static type analysis of programs written to the modified language
definition will be independent of the implementation, particularly, of
the native word size.  This prevents inadvertently writing code that
is highly nonportable among different native word sizes.

The new, not-necessarily-native integer type does not extend to
certain existing uses of INTEGER and CARDINAL that are of unlikely
utility and would add complexity.

Actual statements about the language are numbered.  Comments are
indented more deeply, following a numbered item they apply to.  Some
numbered items are labeled "NO CHANGE", and merely call attention to
the lack of a change, where it is relevant or calls for comment.

Changes to the language proper: 

 1. There is a new builtin type named LONGINT.

 2. FIRST(LONGINT) <= FIRST(INTEGER) and LAST(INTEGER) <= LAST(LONGINT).

      The intent is that INTEGER will remain as the native integer
      size on the implemented processor.  LONGINT might be bigger, but
      not necessarily.  Typically, on a 32-bit processor, LONGINT
      would be 64 bits.  On a 64-bit processor, it could be 64 or 128.

 3. There are new literals of type LONGINT, denoted by following a
    nonempty sequence of digits by either 'l' or 'L'.

      Having distinctly spelled literals preserves Modula-3's clean
      system of referentially transparent typing, i.e, the type of
      every expression is determined by the expression alone, without
      regard to how it is used.  The 3 floating point types already
      follow this principle.  Literals of ambiguous type, combined
      with a system of implicit conversions taken from the context 
      would create a semantic mess.  (e.g. Ada).

      I believe intuitively that Id, LOCK, and LOOP are not members of
      FOLLOW(Number), but need to check this mechanically.  It would
      mean that the new literals can not undermine any existing,
      compilable code.

 4. LONGINT # INTEGER.

      This is true regardless of whether their ranges are equal.  This
      keeps the typing independent of the implementation.  Doing
      otherwise could be a portability nightmare.

 5. LONGINT is an ordinal type. 

      This means the existing rules of assignability will allow
      assignment between LONGINT and its subtypes and INTEGER and its
      subtypes, with the usual runtime value check, when required.

 6. Neither LONGINT nor INTEGER is a subtype of the other.  

      This is true regardless of whether their ranges are equal, in
      part for the same reason the types are unequal.

      Note that, for ordinal types, assignability doesn't actually use
      the subtype relation.  In fact, the one place I can find in the
      present language where subtypes matter for ordinal types is in
      the definition of signatures of operators, etc.  In 2.6.1,
      paragraph 5, operands must have a subtype of the type specified.
      Keeping LONGINT and INTEGER subtype-unrelated keeps this
      statement unambiguous and allows easy specification of the
      operators.

 7. Prefix operators +, -, and ABS can take an operand having a
    subtype of LONGINT, in which case, their result has type LONGINT.

 8. Infix operators +, -, DIV, MOD, MIN, and MAX, can accept a pair of
    operands that are subtypes of a mixture of INTEGER and LONGINT.
    If either is a subtype of LONGINT, the result has type LONGINT,
    otherwise INTEGER.  The result is correct (i.e., no overflow
    occurs) if the result value is a member of the result type.

      With assignment between different subranges, Modula-3 takes the
      view that this is not an implied type conversion at all.
      Instead, the rules have the effect that if the value is a member
      of the LHS type, then it's OK.  I think this is a brilliant
      piece of language design.  Compare to the many pages of
      description that C++ and Java require to define implied type
      conversions in assignments, and they only have a few
      integer-like types, whereas current Modula-3 has, typically,
      ~2^31 subrange types involved.  It's also less
      implementation-oriented, because it doesn't appeal to bit
      representations, etc.

      I resisted allowing mixed sizes of operands, until I realized we
      can do the same thing with operators as with assignment, i.e.,
      just require result values to be in range, without calling
      anything an implied type conversion.

      A compiler can implement this by just doing the arithmetic in
      the same size as the result type.  This means if both operands
      are subtypes of INTEGER, (which will always be the case with
      existing code,) then the native arithmetic will be used, without
      loss of efficiency.

 9. Relational operators =, #, <, >, <=, and >=, can accept a pair of
    operands that are subtypes of a mixture of INTEGER and LONGINT.

      Again, a compiler can figure out how to generate code for this,
      with no loss of efficiency when both are subtypes of INTEGER.

10. The first parameter to FLOAT can have a subtype of either INTEGER
    or LONGINT.

11. FLOOR, CEILING, ROUND, and TRUNC have an optional second
    parameter, which can be either LONGINT or INTEGER, and which
    specifies the result type of the operation.  If omitted, it
    defaults to INTEGER.  The result has this type.

      The default preserves existing code.

12. The result type of ORD is LONGINT if the parameter is a subtype of
    LONGINT, otherwise it remains INTEGER.

      There is really not much programmer value in applying ORD to a
      subtype of either INTEGER or LONGINT, since this is just an
      identity on the value.  It would provide an explicit, widening
      type conversion, and NARROW won't accomplish this, because it is
      only defined on reference types.  However, this rule provides
      consistency, and maybe would simplify some machine-generated
      source code scheme.

      This fails to support an implementation's effort to expand the
      number of values of an enumeration type beyond the current
      implied limitation of the positive native word values.  How
      tough.

13. The first parameter to VAL can be a subtype of either INTEGER or
    LONGINT.

      Beside generalizing the existing uses of VAL, this also allows
      explicit conversions between LONGINT and INTEGER, if there is
      any need for them.

14. (NO CHANGE) The safe definitions of INC and DEC do not change.

      As a consequence of the changes to +, -, ORD, and VAL, the
      existing equivalent WITH-statement definition will generalize to
      mixes of any ordinal type for the first parameter and a subtype
      of either INTEGER or LONGINT for the second.

15. There is a new builtin type named LONGCARD.  It "behaves just
    like" (but is not equal to) [0..LAST(LONGINT)].

      The current CARDINAL has an interesting history.  Originally, it
      was just a predefined name for the type [0..LAST(INTEGER)].  It
      was later changed to be "just like" the subrange, i.e., the two
      are not the same type, but have the same properties in every
      other respect.  The only reason for the change had to do with
      reading pickles, which are completely defined and implemented as
      library code.  The change did affect the type analysis of the
      language, nevertheless.

      We should preserve this property for LONGCARD too.

16. Neither LONGINT, nor any subtype thereof, can be used as the index
    type of an array type.

      One could think about allowing this, but it would require a lot
      of other things to be generalized, and is unlikely to be of much
      use.  

      After the world's having had to learn twice the hard way, what a
      mess that comes from addresses that are larger than the native
      arithmetic size, we are unlikely to see it again.  So, the only
      ways a longer LONGINT index type could be of any use are: 1)
      arrays of elements no bigger than a byte, that occupy more than
      half the entire addressable memory, and you want to avoid
      negative index values, or 2) arrays of packed elements less than
      byte size that occupy at least one eighth of the memory (or some
      mixture thereof).  All these cases also can occur only when
      using close to the maximum addressable virtual memory.  Not very
      likely.

      If you really need 64-bit array subscripts, you will have to use
      an implementation whose native size is 64 bits.

      This also avoids generalizing SUBARRAY, several procedures in
      required interface TEXT, more extensive generalization of
      NUMBER, etc.

17. Neither LONGINT, nor any subtype thereof, can be used as the base
    type of a set type.

      This is similar to the array index limitation.  Sets on base
      types of long range are very unlikely, as they would be too bit.
      The assignability rules should make subranges of INTEGER
      relatively easy to use as set base types instead of short
      subranges of LONGINT.  This also obviates generalizing IN.

18. The result type of NUMBER is LONGCARD if its parameter is a
    subtype of LONGINT, otherwise INTEGER, as currently.

      NUMBER has always had the messy problem that its correct value
      can lie beyond the upper limit of its result type CARDINAL.
      Fortunately, it is rare to use it in cases where this happens.
      The expanded definition still has the equivalent problem, but it
      seems even less likely to actually happen.

      One could consider making NUMBER always return a LONGCARD, which
      would fix the problem for parameters that are INTEGER, but that
      would not preserve existing semantics or efficiency.

19. (NO CHANGE) BITSIZE, BYTESIZE, ADRSIZE, and TYPECODE are unchanged.

      If you really need 64-bit sizes or typecodes, you will have to
      use an implementation whose native size is 64 bits.

20. The statement that the upperbound of a FOR loop should not be
    LAST(INTEGER) also applies to LAST(LONGINT).

      Note that the existing definition of FOR otherwise generalizes
      to LONGINT without change.

Changes to required (AKA standard) interfaces:

21. (NO CHANGE).  The INTEGER parameters to Word.Shift and
    Word.Rotate, and the CARDINAL parameters of Word.Extract and
    Word.Insert are unchanged.

      These are bit numbers.  There is no need for a longer range. 

22. There is a new required interface LongWord.  It almost exactly
    parallels Word, except 1) LongWord.T = LONGINT, and 2) it contains
    new functions ToWord and FromWord, that conversion between the two
    types, using unsigned interpretations of the values.  ToInt may
    produce a checked runtime error, if the result value is not in the
    range of an unsigned interpretation of INTEGER.

      Word.T = INTEGER, so LongWord.T should = LONGINT, for
      consistency.  This means simple assignability tests and
      assignments between the types will use signed interpretations.
      So different functions are needed to do size changes with
      unsigned interpretation.

23. (NO CHANGE) The Base and Precision values in required interfaces
    Real, LongReal, and Extended, keep the type INTEGER.

       There is no need for increased value range here. 

24. (NO CHANGE) Float.Scalb, which has a parameter of type INTEGER,
     and Float.ILogb, whose result type is INTEGER, do not have LONGINT
     counterparts.

       It is difficult to imagine these values needing greater range.

25. Fmt has a new function LongInt, parallel to Int, but replacing
    INTEGER by LONGINT.

26. Lex has a new function LongInt, parallel to Int, but replacing
    INTEGER by LONGINT.

27. There is a new required interface named LongAddress.  It is UNSAFE
    and contains procedures that are equivalents for the 5 unsafe
    ADDRESS arithmetic operations, with LONGINT substituted in place
    of INTEGER in their signatures.  These are given in 2.7 and
    include a +, two overloaded meanings of -, an INC, and a DEC.

      It is remotely conceivable that there could be a target whose
      native address size is longer than its native integer size
      (unlike the reverse.)  In such a case, these operations might be
      needed.

      Four of them could be accommodated by just generalizing the
      INTEGER parameter to allow either INTEGER or LONGINT.  The
      remaining operator subtracts two ADDRESS operands and returns an
      INTEGER.  This can't be generalized using Modula-3's existing
      pattern of overload resolution of builtin operations.
      Redefining it to always do LONGINT arithmetic would violate the
      existing efficiency criterion.  Two separate operations are
      needed.

      This solution avoids complexity in the language proper, while
      still accommodating a rare requirement.  It could probably be 
      left unimplemented unless/until such a target actually happens.

Changes to useful interfaces: 

28. IO has new procedures PutLongInt and GetLongInt, parallel to
    PutInt and GetInt.

      I have not looked systematically at all the useful interfaces
      for other places that use CARDINAL and INTEGER and might need to
      be generalized.  (Can anyone point to a tool that will grep
      files in .ps or .pdf format, or something equivalent?)

      Note that changes in nonrequired interfaces should be
      implementable just by writing new/modified library code, without
      additional help from the compiler.
