Unbounded by reference (The GNU Modula-2 Compiler)

### 2.13 Unbounded by reference ¶

This section documents a GNU Modula-2 compiler switch which implements a language optimization surrounding the implementation of unbounded arrays. In GNU Modula-2 the unbounded array is implemented by utilizing an internal structure `struct {dataType *address, unsigned int high}`. So given the Modula-2 procedure declaration:

    PROCEDURE foo (VAR a: ARRAY OF dataType) ;
    BEGIN
       IF a[2]= (* etc *)
    END foo ;

it is translated into GCC `tree`s, which can be represented in their C form thus:

    void foo (struct {dataType *address, unsigned int high} a)
    {
       if (a.address[2] == /* etc */
    }

Whereas if the procedure `foo` was declared as:

    PROCEDURE foo (a: ARRAY OF dataType) ;
    BEGIN
       IF a[2]= (* etc *)
    END foo ;

then it is implemented by being translated into the following GCC `tree`s, which can be represented in their C form thus:

    void foo (struct {dataType *address, unsigned int high} a)
    {
       dataType *copyContents = (dataType *)alloca (a.high+1);
       memcpy(copyContents, a.address, a.high+1);
       a.address = copyContents;

       if (a.address[2] == /* etc */
    }

This implementation works, but it makes a copy of each non VAR unbounded array when a procedure is entered. If the unbounded array is not changed during procedure `foo` then this implementation will be very inefficient. In effect Modula-2 lacks the `REF` keyword of Ada. Consequently the programmer maybe tempted to sacrifice semantic clarity for greater efficiency by declaring the parameter using the `VAR` keyword in place of `REF`.

The `-funbounded-by-reference` switch instructs the compiler to check and see if the programmer is modifying the content of any unbounded array. If it is modified then a copy will be made upon entry into the procedure. Conversely if the content is only read and never modified then this non `VAR` unbounded array is a candidate for being passed by reference. It is only a candidate as it is still possible that passing this parameter by reference could alter the meaning of the source code. For example consider the following case:

    PROCEDURE StrConCat (VAR a: ARRAY OF CHAR; b, c: ARRAY OF CHAR) ;
    BEGIN
       (* code which performs string a := b + c *)
    END StrConCat ;

    PROCEDURE foo ;
    VAR
       a: ARRAY [0..3] OF CHAR ;
    BEGIN
       a := 'q' ;
       StrConCat(a, a, a)
    END foo ;

In the code above we see that the same parameter, `a`, is being passed three times to `StrConCat`. Clearly even though parameters `b` and `c` are never modified it would be incorrect to implement them as pass by reference. Therefore the compiler checks to see if any non `VAR` parameter is type compatible with any `VAR` parameter and if so it generates run time procedure entry checks to determine whether the contents of parameters `b` or `c` matches the contents of `a`. If a match is detected then a copy is made and the `address` in the unbounded `struct`ure is modified.

The compiler will check the address range of each candidate against the address range of any `VAR` parameter, providing they are type compatible. For example consider:

    PROCEDURE foo (a: ARRAY OF BYTE; VAR f: REAL) ;
    BEGIN
       f := 3.14 ;
       IF a[0]=BYTE(0)
       THEN
          (* etc *)
       END
    END foo ;

    PROCEDURE bar ;
    BEGIN
       r := 2.0 ;
       foo(r, r)
    END bar ;

Here we see that although parameter, `a`, is a candidate for the passing by reference, it would be incorrect to use this transformation. Thus the compiler detects that parameters, `a` and `f` are type compatible and will produce run time checking code to test whether the address range of their respective contents intersect.