Modula-2 Reloaded

A Modern Typesafe & Literate Programming Notation

Site Menu

Project

Specification

Implementation

Recommendations

Reference

Needs Updating

Work in Progress

Wastebasket

Wiki Manual

edit SideBar

Flexible Array Fields In Records

Terminology

Determinate and Indeterminate Types
  • a type is determinate if its allocation size can be determined from its declaration.
  • a type is indeterminate if its allocation size cannot be determined from its declaration.
Array Bounds
  • the lower bound of an array is its lowest legal subscript.
  • the upper bound of an array is its highest legal subscript.
  • a subscript is out of bounds in respect of an array if it is not a legal subscript of the array.
  • an array type whose bounds cannot be determined at compile time is always indeterminate.
Record Types and Fields
  • a determinate field is a record field whose type is determinate.
  • an indeterminate field is a record field whose type is indeterminate.
  • a discriminant field is a field that determines the size of an indeterminate field.
  • a record type that has an indeterminate field is always indeterminate.

Declaration of Indeterminate Record Types

Indeterminate record types may be declared, provided that the type declaration meets all of the following conditions:

  • it contains only one indeterminate field, prefixed by a tilde
  • its indeterminate field is the last field
  • the type of its indeterminate field is an array
  • it also contains a discriminant field of ordinal type
  • its discriminant field is referenced as the indeterminate field's array size

An example declaration of an indeterminate record type Foobar with a discriminant field size and an indeterminate field buffer is shown below:

TYPE Foobar = RECORD
  a, b, c : Foo; (* other fields *)
  size    : CARDINAL; (* discriminant field *)
~ buffer  : ARRAY size OF OCTET; (* indeterminate field *)
END; (* Foobar *)

Allocation

The value returned by TSIZE for an indeterminate record type is the value of its allocation size without the size of the indeterminate field.

Records of an indeterminate type may only be allocated dynamically at runtime using the NEW statement (see issues). When the record is allocated, the discriminant value must be passed to NEW as an additional parameter. Any attempt to allocate a record of indeterminate type without passing the discriminant value results in a compile time error.

The compiler replaces a macro call to NEW for an indeterminate type with a code fragment that calculates the correct allocation size, calls ALLOCATE and initialises the discriminant field of the new record with the discriminant value passed.

The allocation size is calculated by the following formula:

allocSize(T) = TSIZE(T) + discriminant * TSIZE( baseType(T.indeterminateField) )

where T is the indeterminate record type, discriminant is the discriminant value passed to NEW and baseType(T.indeterminateField) is the element type of the indeterminate field.

An example allocation of a new record of indeterminate type Foobar with a discriminant value of 100 is shown below:

VAR foo : POINTER TO Foobar;

BEGIN
  (* allocate a record of type Foobar with a buffer of 100 elements *)
  NEW foo OF 100;

This source fragment is then replaced by the compiler with the following code fragment:

ALLOCATE( foo, TSIZE(foo) + 100 * TSIZE(OCTET) );
IF foo # NIL THEN foo^.size := 100 END;

Immutability of the Discriminant Field

The discriminant field of a record of indeterminate type is automatically initialised when it is allocated. After initialisation the discriminant field becomes immutable and the compiler enforces its immutability as follows:

  • a discriminant field may not be passed to any procedure as a VAR parameter
  • a discriminant field may not occur on the left hand side of an assignment
  • the ++ and -- operations may not be used on a discriminant field

The following statements all result in compile time errors due to immutability of foo^.size:

INC(foo^.size, 10); (* discriminant fields may not be passed as VAR parameter *)
foo^.size := newSize; (* discriminant fields may not be assigned to *)
foo^.size++; (* discriminant fields may not be used with ++ or -- *)

Run-time Bounds Checking

Access to the indeterminate array field of a record of indeterminate type is bounds checked at runtime in the same manner as access to a determinate array is checked. The compiler automatically inserts the code to check array subscripts against the discriminant field. Any attempt to access the array with a subscript that is out of bounds results in a run-time error.

An example of an assignment to a variable size array field is shown below:

foo^.buffer[index] := 0;

generates target code equivalent to

IF index < foo^.size THEN foo^.buffer[index] := 0 ELSE Error(OutOfBounds) END;

Assignment Compatibility

The assignment compatibility of two records of indeterminate type cannot be verified at compile time. For this reason records of indeterminate type can only be copied field-wise, not record-wise.

The following example shows how one record of type Foobar is copied to another:

VAR foo, bar : POINTER TO Foobar;

BEGIN
  foo^.a := bar^.a;
  foo^.b := bar^.b;
  foo^.c := bar^.c;
  FOR index, value IN foo^.buffer DO
    value := bar^.buffer[index]
  END;

The following example will result in a compile time error:

foo^ := bar^; (* records of indeterminate type may not be copied record-wise *)

Parameter passing

Since the compatibility of records of indeterminate types cannot be determined at compile time, it follows that indeterminate record types may not be formal types and records of indeterminate type may not be passed as parameters unless the formal type is CAST ARRAY OF OCTET because this formal type is compatible with any type.

The following example declaration results in a compile time error:

PROCEDURE clear ( f : Foobar );
   (* compile time error: indeterminate record types may not be formal types *)

The indeterminate field of a record of indeterminate type may be passed as a parameter whose formal type is an open array with the same element type as the indeterminate field's array type.

An example of passing the indeterminate field foo^.buffer as a parameter is shown below:

PROCEDURE clear ( VAR buffer : ARRAY OF OCTET );
VAR index : CARDINAL;

BEGIN
  FOR VALUE elem IN buffer DO
    elem := 0;
  END;
END clear;

BEGIN (* module initialisation *)
  clear( foo^.buffer );

Deallocation

Records of indeterminate type may only be deallocated using the RELEASE statement (see issues).

An example deallocation of a record of type Foobar is shown below:

RELEASE foo;

This statement is then transformed by the compiler to the following code fragment

DEALLOCATE( foo, TSIZE(foo) + foo^.size * TSIZE(OCTET) );

Outstanding Issues

The following issues still need to be resolved

Attempt to allocate without the use of NEW

The use of ALLOCATE with records of indeterminate types is insufficient because the determinant field of the allocated record is immutable and must be initialised during allocation.

How should an attempt to allocate a record of indeterminate type using ALLOCATE be handled?

  • require ALLOCATE to initialise all allocated memory to zero (expensive, thus undesirable)
  • generate a compile time error when a pointer to an indeterminate type is passed to ALLOCATE
  • generate a compile time warning when a pointer to an indeterminate type is passed to ALLOCATE
Attempt to deallocate without the use of RELEASE

The use of DEALLOCATE with records of indeterminate types is undesirable because of

  • safety, the actually allocated size, not the return value of TSIZE, must be passed to DEALLOCATE
  • consistency, since NEW must be used for allocation, RELEASE should be used for deallocation

How should an attempt to deallocate a record of indeterminate type using DEALLOCATE be handled?

  • silently tolerate its use (increased risk of runtime errors and inconsistent, thus undesirable)
  • generate a compile time error when a pointer to an indeterminate type is passed to DEALLOCATE
  • generate a compile time warning when a pointer to an indeterminate type is passed to DEALLOCATE
Obtaining the actual allocation size of records of indeterminate type

Do we need to reintroduce SIZE to obtain the actual allocation size? Probably yes.