Super Prev Next

Mapping to C

Almost all features of Modula-2 and Oberon-2 have direct equivalents in ANSI C. If some construct is not directly available in C, the most simple and effective solution preserving the language semantics is used.

Many features are implemented in the run-time system. The file X2C.h is a C header file of the run-time support library. It contains a set of type definitions, macros and functions necessary for compilation and execution of a translated code.

Note: In the examples of a generated C code throughout this appendix some unimportant details may be omitted for simplicity.


Super Prev Next

Layout of a generated code

The compiler generates the header files and C code files (See also Files generated during compilation). A generated header file has the following general layout:

  1. a user defined copyright statement (See the COPYRIGHT equation)
  2. two header lines, including time of compilation, the name of the file, a version of the XDS compiler.
  3. #ifdef <module_name>_H_
  4. a set of "include" directives (always contains "include" directive for the run-time header file X2C.h).
  5. external declarations
  6. #endif

Note: External declarations may contain implicitly exported identifiers. E.g. a structure always contains non-exported fields.

For a sample definition module:

DEFINITION MODULE MyLib;

PROCEDURE Foo;

END MyLib.

the following header file will be produced under on assumption that the COPYRIGHT equation is properly set):

/* (c) 1994 Widget Databases Ltd */
/* "@(#)MyLib.h Sep 15 12:50:16 1995" */
/* Generated by XDS Modula-2 to ANSI C v3.14 translator */

#ifndef MyLib_H_
#define MyLib_H_
#ifndef X2C_H_
#include "X2C.h"
#endif

extern void X2C_PROCLASS MyLib_Foo(void);

extern void X2C_PROCLASS MyLib_BEGIN(void);

#endif /* MyLib_H_ */

A generated C code file has the following general layout:

  1. a user defined copyright statement (See the COPYRIGHT equation)
  2. two header lines, including time of compilation (See the option GENDATE), the name of the file, a version of the XDS compiler.
  3. definitions of the pre-processor symbols corresponding to the settings of some options (See Describing platform)
  4. a set of include directives (for program module it contains "include" directive for the run-time header file X2C.h).
  5. the generated source text

For the implementation module:

IMPLEMENTATION MODULE MyLib;

IMPORT  InOut;

VAR count: INTEGER;

PROCEDURE Foo;
BEGIN
  INC(count);
  InOut.WriteInt(count,0);
  InOut.WriteLn;
END Foo;

BEGIN
  count:=0;
END MyLib.

the following code file will be produced:

/* (c) 1994 Widget Databases Ltd */
/* "@(#)MyLib.c Jan 12 12:50:36 1995" */
/* Generated by XDS Modula-2 to ANSI C v3.12 translator */

#define X2C_int32
#define X2C_index32
#ifndef InOut_H_
#include "InOut.h"
#endif
#include "MyLib.h"

static X2C_INT32 count;

extern  void X2C_PROCLASS MyLib_Foo(void)
{
  count += 1l;
  InOut_WriteInt(count,0ul);
  InOut_WriteLn();
} /* END Foo */

extern void X2C_PROCLASS MyLib_BEGIN(void)
{
  static int MyLib_init=0;
  if (MyLib_init) return;
  MyLib_init=1;
  InOut_BEGIN();
  count=0ul;
}

Note: for an Oberon module both the header and code files are generated.


Super Prev Next

Identifiers

The compiler tries to copy identifiers from the source text to the C code without modifications. In some cases it can be necessary to expand an identifier or to reduce it (See also the GENIDLEN equation).

Note: The compiler does not change identifiers which are marked as C identifiers via the direct language specification facility. See Direct language specification.

All exported identifiers /Identifiers that are declared in a definition module, or marked as exported in an oberon module./ are prefixed by the module name.

If an identifier to be declared is already defined in the C text it is postfixed with a number. It can occur for a various reasons:

  1. an identifier coincides with the C keyword or standard identifier.

      VAR char: CHAR;
    

    translates to:

      X2C_CHAR char1;
    

    The compiler uses postfixing for all identifiers listed in the xm.kwd file. The file provided by the distribution contains a list of all ANSI C/C++ keywords and some identifiers from the C standard libraries.

    One can extend the file with other identifiers. For example, if you program contains (non-exported) identifier pi and imports a standard C math library, it is necessary to include pi into the xm.kwd file.

  2. a local identifier coincides with the global one.

    VAR i: INTEGER;
    
    PROCEDURE Foo;
      VAR i: INTEGER;
    BEGIN
      i:=1;
    END Foo;
    

    translates to:

    static X2C_INT32 i;
    
    static void Foo(void)
    {
      X2C_INT32 i1;
      i1 = 1;
    }
    
  3. an identifier is exported/imported to/from the context, where such an identifier is already defined. E.g.

    PROCEDURE Foo; END Foo;
    
    MODULE Local;
    
    EXPORT QUALIFIED Foo;
    
    PROCEDURE Foo; END Foo;
    
    END Local;
    

    The compiler does not extend identifiers defined in the local modules with the module name. It will use postfixing to distinguish between two Foo procedures.

    static void Foo(void)
    {
    }
    
    static void Foo1(void) /* Local.Foo */
    {
    }
    

If the length of a generated identifier length is greater than the limit defined by the GENIDLEN equation, the compiler will reduce the identifier. Let us consider the definition module:

DEFINITION MODULE MyModule;

VAR int: INTEGER;

PROCEDURE proc;

END MyModule.

If the limit is large enough (in our case greater than 13), the following declarations will be generated in the header file:

extern X2C_INT32 MyModule_int;
extern void X2C_PROCLASS MyModule_proc(void);

If the GENIDLEN equation is set to 6, all identifiers will be reduced:

extern X2C_INT32 MyModu;
extern void X2C_PROCLASS MyMod1(void);

This feature can be used for satisfying the obsolete C compilers or linkers, which imposes strong restrictions on the length of identifiers.

A special naming scheme is used for the identifiers of the functions corresponding to the initialization parts of compilation units (module bodies). If the option VERSIONKEY is off, the compiler generates a function identifier of the form:

<module_name>_BEGIN

For the above example, the initialization part will be declared as

extern void X2C_PROCLASS MyModule_BEGIN(void);

or, if GENIDLEN=6, as

extern void X2C_PROCLASS MyMod2(void);

If the option is on, the compiler generates the name of a module body as a composition of

If the definition (or Oberon) module imported by different compilation units has the same version, the same name will be generated for each call of the module body. In all other cases unresolved references will occur at a link time.

Super Prev Next

Example

extern void X2C_PROCLASS MyModule_BEGIN_A0FE6691B(void);

or, if GENIDLEN=6, as

extern void X2C_PROCLASS MyMod2_A0FE6691B(void);

We recommend to switch ON the option VERSIONKEY whenever possible.


Super Prev Next

Data types


Super Prev Next

Basic types

The correspondence between Modula-2/Oberon-2 basic types and C types is described in the tables 13. Representation of Modula-2 basic types and 14. Representation of Oberon-2 basic types. A representation of system types is described in the table 15. Representation of SYSTEM types. Note: Subrange types are represented by their host types.

If the option GENCTYPES is off, the compiler uses identifiers defined the in run-time module X2C.h for all basic types; see the last column of the tables. If the option is on, the compiler generates C type identifiers.

VAR ch: CHAR;

translates to (GENCTYPES is off):

X2C_CHAR ch;

or, if the option is on, to:

char ch;

Table 13. Representation of Modula-2 basic types

Basic type Bits C type X2C type
SHORTINT 8 signed char X2C_INT8
INTEGER 16 short X2C_INT16
INTEGER 32 long X2C_INT32
LONGINT 32 long X2C_INT32
SHORTCARD 8 unsigned char X2C_CARD8
CARDINAL 16 unsigned short X2C_CARD16
CARDINAL 32 unsigned long X2C_CARD32
LONGCARD 32 long X2C_CARD32
REAL 32 float X2C_REAL
LONGREAL 64 double X2C_LONGREAL
CHAR 8 unsigned char X2C_CHAR
BOOLEAN 8 unsigned char X2C_BOOLEAN
BITSET 16 unsigned short X2C_SET16
BITSET 32 unsigned long X2C_SET32

In Modula-2, the size of INTEGER, CARDINAL and BITSET types is controlled via the option M2BASE16. If the option is set, INTEGER is equal to SYSTEM.INT16, CARDINAL is equal to SYSTEM.CARD16 and BITSET is defined as PACKEDSET OF [0..15]. Otherwise, all these types are 32-bits wide.

Table 14. Representation of Oberon-2 basic types

Basic type Bits C type X2C type
SHORTINT 8 signed char X2C_INT8
INTEGER 16 short int X2C_INT16
LONGINT 32 long int X2C_INT32
REAL 32 float X2C_REAL
LONGREAL 64 double X2C_LONGREAL
CHAR 8 unsigned char X2C_CHAR
BOOLEAN 8 unsigned char X2C_BOOLEAN
SET 32 unsigned long X2C_SET32

Table 15. Representation of SYSTEM types

System type Bits C type X2C type
INT8 8 signed char X2C_INT8
INT16 16 short int X2C_INT16
INT32 32 long X2C_INT32
CARD8 8 unsigned char X2C_CARD8
CARD16 16 unsigned short X2C_CARD16
CARD32 32 unsigned long X2C_CARD32
LOC 8 char X2C_LOC
BYTE 8 char X2C_LOC
WORD 32 array of LOC X2C_WORD
ADDRESS 32 pointer to LOC X2C_ADDRESS


Super Prev Next

Special system types

The module SYSTEM provides special types int, unsigned, size_t and void. A characteristic feature of these types is that they are generated exactly as the corresponding C types, i.e.

VAR
  x: SYSTEM.size_t;
  y: SYSTEM.int;
  z: POINTER TO SYSTEM.void;

translates to

  size_t x;
  int y;
  void * z;

The types should be used in the foreign definition modules (See Interfacing to C).


Super Prev Next

Modula-2 enumeration types

An enumeration type is translated to the C enum declaration.

TYPE color = (red,green,blue);

translates to:

enum color {red,green,blue};

or, if the option GENTYPEDEF is set to:

enum color {red,green,blue};
typedef enum color color;


Super Prev Next

Modula-2 set types

Modula-2 sets which have not more than 32 elements are represented as unsigned types of appropriate length. Large sets are declared as array of words.

TYPE
  SmallSet = SET OF [-1..1];
  Set16    = SET OF [0..15];
  LongSet  = SET OF [-1..32];

translates to:

  typedef X2C_SET8 SmallSet;
  typedef X2C_SET16 Set16;
  typedef X2C_CARD32 Long[2];


Super Prev Next

Record types

A record is translated into a C struct.

TYPE
  R = RECORD
    b: BOOLEAN;
    c: CHAR;
  END;

translates to:

  struct R {
    X2C_BOOLEAN b;
    X2C_CHAR c;
  };

If the option GENTYPEDEF is set, the compiler will generate the typedef declaration including both tag name and type name.

  struct R;
  typedef struct R R;
  struct R {
    X2C_BOOLEAN b;
    X2C_CHAR c;
  };

The tag names are needed for the recursive structure declarations (See the next section).

A dummy field is generated for an empty record, since the C compilers treat an empty structure as an error:

  struct R {
    X2C_INT32 _dummy_;
  }

A variant part is translated to a C union.

  R = RECORD
    CASE tag: BOOLEAN OF
    |TRUE : c: CHAR;
    |FALSE: b: BOOLEAN;
    END;
    set: BITSET;
  END;

is translated to:

  struct R {
    X2C_BOOLEAN tag;
    union {
      struct {
        X2C_CHAR c;
      } _1;
      struct {
        X2C_BOOLEAN b;
      } _2;
    } _;
    X2C_SET32 set;
  };

An access to a field of a variant part (r is of type R)

  r.c:='a';

is translated to:

  r._._1.c:='a';


Super Prev Next

Pointer types

A pointer type is mapped to the corresponding C type.

  P = POINTER TO R;
  R = RECORD
    next: P;
  END;

is translated to:

  struct R;
  typedef struct R *P;
  struct R {
    P next;
  };

Pointer types are often used in a declaration of recursive data structures. In C, a recursive data structure must contain at least one struct declaration. The compiler reports an error if detects a recursive data type without at least one record type, e.g.:

  T = POINTER TO T;

This limitation should not cause any problems since data structures that constitutes of pointers only are quite artificial.

A special case is a pointer to an open array (See Dynamic arrays).


Super Prev Next

Dynamic arrays

A dynamic array type /Pointer to a (multidimensional) open array./ is represented as a pointer to a descriptor of an open array. For an N-dimensional open array, the descriptor contains:

Super Prev Next

Example

TYPE
  String = POINTER TO ARRAY OF CHAR;
  Matrix = POINTER TO ARRAY OF ARRAY OF REAL;

is translated to:

  struct _0;
  typedef struct _0 * String;

  struct _0 {
    X2C_CHAR * Adr; /* pointer to an array body */
    X2C_INDEX Len0;
  };

  struct _1;
  typedef struct _1 * Matrix;

  struct _1 {
    X2C_REAL * Adr;  /* pointer to an array body */
    X2C_INDEX Len0;  /* length of the 1st dimension */
    X2C_INDEX Size1;
    X2C_INDEX Len1;  /* length of the 2nd dimension */
  };

If m is of type Matrix then the call NEW(m,3,5) will set the following values:

Len0 5 a length of the inner dimension
Len1 3 a length of the outer dimension
Size1 20 5·4, if sizeof(REAL)=4


Super Prev Next

Procedure declarations

A Modula-2/Oberon-2 procedure is translated to a C function. A special case is translation of a nested procedure (See Nested procedures). A generated function prototype includes call of the X2C_PROCLASS macro, if the GENPROCLASS option is ON.

Regardless of the GENPROCLASS setting, function prototypes corresponding to foreign procedures contains the following macro call:

DLS string Macro name
"C" none
"Pascal" X2C_PASCAL
"StdCall" X2C_STDCALL
"SysCall" X2C_SYSCALL

A list of parameters is generated according to the value of the GENKRC option (See Code generation). If GENKRC is ON, the compiler generates parameter names only, otherwise the compiler generates full function prototype.


Super Prev Next

Parameters

The parameter passing convention conforms, whenever possible, to the rules of the C language. Variable parameters are declared as pointers to the formal parameter type. Exceptions are parameters of array and large set types which are always passed by reference.

The procedure header

PROCEDURE Foo(a: INTEGER;
          VAR b: INTEGER;
              c: Array;
          VAR d: Array);

is translated to:

void X2C_PROCLASS Foo(X2C_INT32 a,
                      X2C_INT32 *b,
                      Array c,
                      Array d)

In the case of value arrays and long sets, the procedure called is responsible for making a local copy.

TYPE Vector = ARRAY [0..2] OF REAL;

PROCEDURE Foo(v: Vector);
BEGIN
  <statements>
END Foo;

is translated to:

typedef X2C_REAL Vector[3];

static void X2C_PROCLASS Foo(Vector v)
{
  V tmp;
  v = (X2C_REAL *)memcpy(tmp,v2,sizeof(V));
  <statments>
} /* END Foo */

A special case is a character array parameter of a fixed size. A string literal can be passed as an actual parameter for a such formal parameter. A string literal can be shorter than the formal parameter and a special care must be taken not to access memory location beyond the end of the actual parameter. The compiler copies a string literal to a temporary variable in the caller procedure. Then in the callee procedure, the parameter will be copied again according to the standard rules. Note: in some cases this double copying may be prevented by marking the parameter as read-only (See Read-only parameters).

TYPE Str = ARRAY [0..7] OF CHAR;

PROCEDURE Foo(s: Str);
END Foo;

PROCEDURE Callee;
BEGIN
  Foo("hello");
END Callee;

is translated to:

typedef X2C_CHAR Str[8];

static void X2C_PROCLASS Foo(Str s)
{
  A tmp;
  s=(X2C_CHAR *)memcpy(tmp,s,8u);
} /* END Foo */

static void X2C_PROCLASS Callee(void)
{
  Str tmp;
  Foo(*(Str *)memcpy(&tmp,"hello",6u));
} /* END Callee */


Super Prev Next

Open arrays

Parameters of an open array are generated according to the following rules:

PROCEDURE Foo(s: ARRAY OF ARRAY OF CHAR);
BEGIN
  <statements>
END Foo;

PROCEDURE Callee;
  VAR x: ARRAY [0..1],[0..1] OF CHAR;
BEGIN
  Foo(x);
END Callee;

is translated to:

static void X2C_PROCLASS Foo(X2C_CHAR s[],
                             X2C_CARD32 s_len,
                             X2C_CARD32 s_len1)
{
  X2C_PCOPY((void **)&s,s_len*s_len1);
  <statements>
  X2C_PFREE(s,s_len*s_len1);
} /* END Foo */

static void X2C_PROCLASS Callee(void)
{
  X2C_CHAR x[2][2];
  Foo((X2C_CHAR *)x, 2u, 2u);
} /* END Callee */


Super Prev Next

Oberon-2 variable vecord parameters

For a variable record parameter in Oberon-2 an additional parameter (type tag) is passed. This parameter is needed for the dynamic type tests and for calling the type-bound procedures (See also Oberon-2 object-oriented features).

PROCEDURE Foo(VAR r: Rec);

is translated to:

static void proc(struct Rec * r, X2C_TD r_type);


Super Prev Next

Sequence parameters

For the sequence parameters, the compiler forms the byte array explicitly as a dynamic aggregate, according to the rules specified in Variable number of parameters.

PROCEDURE write(SEQ x: SYSTEM.BYTE);
END write;

PROCEDURE Foo;
  VAR
    i: INTEGER;
    c: CHAR;
    r: REAL;
    a: ARRAY [0..7] OF CHAR;
BEGIN
  write(i,c,r,a);
END Foo;

is translated to:

static void X2C_PROCLASS write(X2C_LOC x[],
                               X2C_CARD32 x_len)
{
} /* END write */

static void X2C_PROCLASS Foo(void)
  X2C_INT32 i;
  X2C_CHAR c;
  X2C_REAL r;
  X2C_CHAR a[8];
  X2C_SEQ tmp[7];
  write(
    (tmp[0].val=i,
     tmp[1].val=(X2C_CARD32)c,
     *(X2C_LONGREAL*)&tmp[2]=(X2C_LONGREAL)r,
     tmp[4].adr=a,
     tmp[5].val=0,
     tmp[6].val=7,
     (X2C_LOC *)tmp),
     28u);
} /* END Foo */

For this call, the actual array passed to write will contain:


Super Prev Next

Function results

XDS supports arbitrary return types for functions. If a function returns an array or a large set type, an additional parameter is declared. It is used as a pointer to a temporary variable receiving the result of function.

TYPE A = ARRAY [0..1] OF REAL;

PROCEDURE Foo(): A;
  VAR a: A;
BEGIN
  RETURN a
END Foo;

PROCEDURE Callee;
  VAR x: A;
BEGIN
  x:=Foo();
END Callee;

is translated to:

typedef X2C_REAL A[2];

static X2C_REAL * X2C_PROCLASS Foo(A Foo_ret)
{
  A a1;
  memcpy(Foo_ret,a1,sizeof(A));
  return Foo_ret;
} /* END Foo */

static void X2C_PROCLASS Callee(void)
{
  A x;
  A tmp;
  memcpy(x,Foo(tmp),sizeof(A));
} /* END Callee */


Super Prev Next

Procedure body

In most cases the translation of a procedure body is transparent. Most statements of the source languages have direct analog in C. However, in some cases a special care must be taken to preserve the language semantics.

The following example illustrates the situation where return statements are replaced with goto to free the memory allocated for a parameter.

PROCEDURE Length(s: ARRAY OF CHAR): CARDINAL;
  VAR i: CARDINAL;
BEGIN
  i:=0;
  WHILE i<HIGH(s) DO
    IF s[i]=0C THEN RETURN i END;
    INC(i)
  END;
  RETURN i
END Length;

is translated to:

static X2C_CARD32 Length(X2C_CHAR s[],
                         X2C_CARD32 s_len)
{
  X2C_CARD32 i;
  X2C_CARD32 Length_ret;
  X2C_PCOPY((void **)&s,s_len);
  i = 0;
  while (i<s_len-1) {
    if (s[i]=='\0') {
      Length_ret=i;
      goto label;
    }
    i += 1;
  } /* END WHILE */
  Length_ret=i;
  label:;
  X2C_PFREE(s,s_len);
  return Length_ret;
} /* END Length */

If a procedure contains local modules, its initialization and finalization parts (See Finalization) are inserted into the appropriate places:

VAR Foo_in_operation: BOOLEAN;

PROCEDURE Foo(): INTEGER;
  MODULE M;
    IMPORT Foo_in_operation;
  BEGIN
    Foo_in_operation:=TRUE;
  FINALLY
    Foo_in_operation:=FALSE;
  END M;
BEGIN
  RETURN 1
END Foo;

is translated to:

static X2C_BOOLEAN Foo_in_operation;

static X2C_INT32 Foo(void)
{
  X2C_INT32 Foo_ret;
  Foo_in_operation=1; /* M initialization */
  Foo_ret=0x01l;
  Foo_in_operation=0; /* M finalization */
  return Foo_ret;
} /* END Foo */

If a procedure contains an exceptional part (See Exceptions), the procedure body is generated as if statement, where one branch corresponds to a normal part, and another to an exceptional part. The calls of run-time functions are generated to provide all necessary actions. The finalization statements (if any) are generated after the if statement.

PROCEDURE Div(a,b: INTEGER): INTEGER;
BEGIN
  RETURN a DIV b
EXCEPT
  RETURN MAX(INTEGER)
END Div;

is translated to:

static X2C_INT32 Div(X2C_INT32 a, X2C_INT32 b)
{
  X2C_XHandler_STR tmp;
  X2C_INT32 Div_ret;
  if (X2C_XTRY(&tmp)) {
    Div_ret = X2C_DIV(a,b);
    X2C_XOFF();
  }
  else {
    Div_ret=X2C_max_longcard;
  }
  X2C_XREMOVE();
  return Div_ret;
} /* END Div */


Super Prev Next

Nested procedures

There is no equivalent for Modula-2/Oberon-2 nested procedures in C. The compiler appends additional parameters to make an access to the local variables (and parameters) of an outer procedure(s).

Super Prev Next

Example

PROCEDURE proc(a: INTEGER);
  VAR b,c,d: INTEGER;

  PROCEDURE loc1(a: INTEGER);
  BEGIN
    b:=a;
  END loc1;

  PROCEDURE loc2;
  BEGIN
    loc1(d+a);
  END loc2;

BEGIN
  c:=1;
  loc2;
END proc;

is translated to:

static void loc1(X2C_INT32 * b, X2C_INT32 a)
{
  *b=a;
} /* END loc1 */

static void loc2(X2C_INT32 * b,
                 X2C_INT32 * a,
                 X2C_INT32 * d)
{
  loc1(b, *d+*a);
} /* END loc2 */

static void X2C_PROCLASS proc(X2C_INT32 a)
{
  X2C_INT32 c;
  X2C_INT32 b;
  X2C_INT32 d;
  c=0x01l;
  loc2(&b, &a, &d);
} /* END proc */

Note: Only the used variables are passed as additional parameters (e.g. the variable c is not used and not passed).


Super Prev Next

Module initialization and finalization

For each compilation unit the compiler generates the initialization function which contains the necessary initialization statements and statements constituting the module body. Initialization statements include the call of initialization functions of all imported modules. Two forms of an ideintifier of an initialization function are controlled by the VERSIONKEY option.

If the module body contains a finalization part (See Finalization) this part is generated as a separate procedure, and the run-time support procedure is called to register it.

Super Prev Next

Example

IMPLEMENTATION MODULE M;

IMPORT  A, B;

BEGIN
  A.Foo();
FINALLY
  B.Foo();
END M.

is translated to (VERSIONKEY is OFF; the module header is omitted):

static void final(void)
/* finalization part */
{
  B_Foo();
} /* END */

void M_BEGIN(void)
{
  static int M_init=0;
  if (M_init) return;
  M_init=1;
  A_BEGIN();           /* initialize A */
  B_BEGIN();           /* initialize B */
  X2C_FINALLY(final);  /* register FINALLY */
  A_Foo();             /* M initialization */
}

Each module initialization is executed only once (See the first three lines in M_BEGIN). Imported compilation units are initialized before the body of the module.

For a program module (or an Oberon-2 module marked with the MAIN option) the compiler uses the identifier main as the name of module body, and the main function contains a call of X2C_INIT to initialize run-time system.


Super Prev Next

Oberon-2 object-oriented features

The compiler uses the standard scheme to implement object-oriented features in Oberon-2. A dynamic type of records is needed for type tests and a table of type-bound procedures (methods or virtual functions) is needed to call them. A type descriptor containing all necessary information is created for an Oberon-2 record (See Oberon-2 run-time data structures).

A C structure corresponding to an Oberon-2 record does not contain any additional fields /Unlike the previous XDS release./ since, for variables of a record type, the dynamic type is statically known to the compiler. A dynamic type of an object v may differ from a static one only if v is a variable parameter of a record type or v is a pointer. For a variable parameter of a record type an additional parameter (type tag or a pointer to type descriptor) is passed (See Oberon-2 variable vecord parameters).

For a dynamically allocated record, a type tag (and possibly some other information) is stored before the actual record data and is invisible to a programmer.

An extended record directly contains the fields of all the base types.

TYPE
  Node = POINTER TO NodeDesc;
  NodeDesc = RECORD
    next: Node;
  END;
  IntNodeDesc = RECORD (NodeDesc)
    val: INTEGER;
  END;

is translated to:

struct NodeDesc;
typedef struct NodeDesc * Node;

struct x_NodeDesc {
  Node next;
};

struct x_IntNodeDesc {
  Node next;
  X2C_INT16 val;
};

A type-bound procedure is translated to a function which prototype includes both a receiver parameter (two parameters if receiver is a variable record parameter) and normal parameters.

PROCEDURE (n: Node) Print;
END Print;

is translated to a function declaration and the corresponding function type:

typedef void (X2C_PROCLASS *Print_)(Node);

static  void X2C_PROCLASS Print(Node n)
{
} /* END Print */

The call of type-bound procedure is made via a table of methods (node is of type Node).

  node.Print;

is translated to the call of run-time macro:

  X2C_CALL(Print_,X2C_GET_TD(node),0)(node);

X2C_CALL macro evaluates a procedure to call; X2C_GET_TD function returns type tag; Print_ is the function type and 0 is the ordinal number of the Print method.


Super Prev Next

Oberon-2 run-time data structures

Certain information about an Oberon-2 module must be available at run-time. This information is provided in the form of type descriptors (for each record type) and a module descriptor.

A type descriptor contains an information necessary for:

The run-time system provides a set of pre-defined descriptors, that are used for dynamically allocated arrays (including dynamic arrays).

A module descriptor contains an information necessary for garbage collection (the locations of all global pointers) and meta-language programming.

Super Prev Next

Example

MODULE x;

TYPE
  Node = POINTER TO NodeDesc;
  NodeDesc = RECORD
    next: Node;
  END;
  IntNode = POINTER TO IntNodeDesc;
  IntNodeDesc = RECORD (NodeDesc)
    val: INTEGER;
  END;

VAR root: Node; (* global pointer *)

PROCEDURE (n: Node) Print;
END Print;

PROCEDURE (n: IntNode) Print;
END Print;

PROCEDURE (n: IntNode) Foo;
END Foo;

END x.

The following data structure will be created for the above example (type descriptor of NodeDesc and some details are omitted, comments are added by hand):

/* Module descriptor: */
static void * x_offs[]={ &root,X2C_OFS_END };
static X2C_MD_REC x_desc={
  0,"x",x_offs
};

/* IntNodeDesc type descriptor: */

/* location of pointers (IntNodeDesc): */
static void * x_IntNodeDesc_offs[]= {
        X2C_OFS(struct x_IntNodeDesc,next),
        X2C_OFS_END };

/* method table: */
static X2C_PROC x_IntNodeDesc_proc[]= {
        (X2C_PROC) Print1
        (X2C_PROC) Foo };

/* IntNodeDesc type descriptor: */
extern X2C_TD_REC x_IntNodeDesc_desc={
  sizeof(struct x_IntNodeDesc),
  "IntNodeDesc",
  &x_desc,
  0,1,1,
  /* base type descriptors */
  { &x_NodeDesc_desc, &x_IntNodeDesc_desc,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
  },
  x_IntNodeDesc_proc, /* method table */
  x_IntNodeDesc_offs  /* pointer locations */
};


Super Prev Next

Options

This sections describes the options that affect the generated C code. Some of the options must remain the same for all modules that belongs to the same program (See Describing platform).


Super Prev Next

Describing platform

The XDS compilers generate a highly portable ANSI C code. However, to get correct program one must appropriately describe target platform configuration (or a C compiler) by using the following options (all options are OFF by default):

TARGET16

The option must be set for 16-bit platform. If the option is ON, the compiler will assume the C int type is 16 bits wide. Note: different settings may be required for different C compilers; consult your C compiler manual.

INDEX16

The option defines a size of an index and the maximum size of an array or structure on the target platform. If the option is ON, the compiler will assume that an index is 16 bits wide, otherwise 32 bits wide.

DIFADR16

The option defines a difference between addresses on the target platform. If the option is ON, the compiler assumes that the difference is a 16-bit integer value, otherwise a 32-bit integer value.

The correct setting of the option is required to implement ADDADR, SUBADR and DIFADR system functions.

These options must have the same settings for all the modules of a program. Note: the compiler inserts a definition of the corresponding macros in the C code file.

#define X2C_int32     /* target 32 */
#define X2C_index32   /* index  32 */

If the VERSIONKEY option is set, an identifier of an initialization function contains settings of these options in a packed form.


Super Prev Next

Code generation


Super Prev Next

Improving readability

By default the compiler performs a set of optimizations, including constant expression evaluation, constant propagation, etc. In the expressions it generates constant values, not constant names.

If you are using XDS as a translator, we recommend to set the option NOOPTIMIZE ON. In this case, the compiler generates constant names whenever possible.


Super Prev Next

Generate C types

The GENCTYPES option forces the compiler to use C type identifiers instead of the identifiers, defined in X2C.h, see tables 13. Representation of Modula-2 basic types, 14. Representation of Oberon-2 basic types and 15. Representation of SYSTEM types.

Note: Modula-2 INTEGER, CARDINAL and BITSET types are translated according to the value of the M2BASE16 option. In spite of the option setting, the compiler generates the identifiers from X2C.h for the following types:

Source type X2C type
WORD X2C_WORD
ADDRESS X2C_ADDRESS
PROC X2C_PROC
COMPLEX X2C_COMPLEX
LONGCOMPLEX X2C_LONGCOMPLEX


Super Prev Next

Debug mode

The GENDEBUG option should be set to compile your program in the debug mode. If your program is compiled in this mode, the run-time system will print a stack of procedure calls (a file name and a line number) on abnormal termination of your program.

Super Prev Next

Example

<*+ GENDEBUG *>
MODULE test;

PROCEDURE Div(a,b: INTEGER): INTEGER;
BEGIN
  RETURN a DIV b
END Div;

PROCEDURE Try;
  VAR res: INTEGER;
BEGIN
  res:=Div(1,0);
END Try;

BEGIN
  Try;
END test.

When this program is running, the exception is raised and the run-time system prints the exception location and a stack of procedure calls. If the option LINENO is ON, all information will be reported in terms of original (Oberon-2/Modula-2) source files:

#RTS: No exception handler #6: zero or negative divisor.
test.mod              6
test.mod             12
test.mod             16

The exception was raised in line 6 of test.mod, the Div procedure was called from line 12, while the Try procedure was called from line 16 (module body).

If the option LINENO is OFF, all information will be reported in terms of generated C files:

#RTS: No exception handler #6: zero or negative divisor.
test.c               17
test.c               27
test.c               36

In the debug mode the compiler inserts additional calls in the generated C code (some parts of code unimportant for this example are omitted):

#define X2C_DEBUG

static  long X2C_PROCLASS Div(long a, long b)
{
  long Div_ret;
  X2C_PROC_INP();
  Div_ret=X2C_DIV(a,b);
  X2C_PROC_OUT();
  return Div_ret;
} /* END Div */

static  void X2C_PROCLASS Try(void)
{
  long res;
  X2C_PROC_INP();
  res = (X2C_SET_HINFO() Div(1l, 0l));
  X2C_PROC_OUT();
} /* END Try */

As can be seen from the above example, the compiler

  1. inserts the #define X2C_DEBUG line in the module header. This definition switches all macros, defined in X2C.h run-time library, into the debug mode.
  2. generates calls of X2C_PROC_INP and X2C_PROC_OUT into each procedure declaration.
  3. inserts X2C_SET_HINFO macro (set history information) into each procedure call.

Important notes:


Super Prev Next

Profile mode

If the option GENPROFILE is set ON, the compiler generates additional code to provide a profile of the program. At the end of a program execution the run-time system will print the profile of the program to standard output. For each procedure in the program the output includes:

The option may be not implemented for all platfomrs. See your on-line documentation.


Super Prev Next

Generate K&R C

The option GENKRC forces the compiler to generate K&R C instead of ANSI C. If the option is ON, the procedure declaration

PROCEDURE Foo(a,b: INTEGER);
BEGIN
  ...
END Foo.

is translated to:

static void X2C_PROCLASS Foo(a, b)
  X2C_INT32 a;
  X2C_INT32 b;
{
 ...
} /* END Foo */

By default the option is OFF and XDS generates the ANSI C code.


Super Prev Next

Generate C++

The option GENCPP forces the compiler to generate C++ instead of ANSI C. In the current release there are only few differences between generated ANSI C and C++ code. In the future versions we will use specific C++ features to improve readability and efficiency of generated code.


Super Prev Next

Procedure class specification

The option GENPROCLASS forces the compiler to insert special macro X2C_PROCLASS into all function prototypes.

This macro can be used as a specifier of a function class, e.g. it can be set to pascal to change parameter passing convention. For many platforms, this macro cannot be used in a meaningful way. In such cases, we recommend to set the option OFF to make the generated code more readable.


Super Prev Next

Generate typedef

The option GENTYPEDEF specifies the generation of a record, enumerations and dynamic arrays. If the option is OFF, the compiler generates only tag names in the corresponding C constructs.

  struct Rec {...};
  enum Color {...};

If the option is ON, the compiler generates both tag names and type names, using typedef.

  struct Rec {...};
  typedef struct Rec Rec;
  enum Color {...};
  typedef enum Color Color;

The option is essential in the development of an interface to a foreign library. An inline usage of the option is recommended in such cases. The following examples (from stdio.h and time.h illustrate inline usage.

From the interface to stdio.h:

TYPE
<* PUSH *> <* GENTYPEDEF+ *>
  FILE = RECORD END;
<* POP *>

The option shall be ON, since FILE is usually defined as

typedef struct {...} FILE;

Vice versa, we have to switch the option OFF, when defining the interface to time.h:

TYPE
<* PUSH *> <* GENTYPEDEF- *>
  tm = RECORD
    ...
  END;
<* POP *>

since it is defined as

struct tm {...};


Super Prev Next

Version key

The VERSIONKEY option specifies the generation of an identifier for an initialization function. The option is introduced to perform version checks at a link time. If the symbol file (corresponding to a definition or Oberon module) has the same version, the same names will be generated for the calls of initialization functions. If the name of an actual initialization function and the name of the called function does not match the linker will report the unresolved reference error. It means that the program must be recompiled in the right order using MAKE or PROJECT operation mode (See Chapter Using the compiler).

Note: the option should be set when a definition or an Oberon module is compiled. See also Identifiers for further details.


Super Prev Next

The length of identifiers

The GENIDLEN sets the maximum length of an identifier in the generated code. Note: the identifier’s length cannot be less than 6 characters. The small value leads to more compact but non-readable text. See also Identifiers.


Super Prev Next

Evaluating size of types

The programmers are used to the fact that the sizes of types are known at the compilation time, i.e. one can write

TYPE Rec = RECORD ... END;

CONST Size = SIZE(Rec);

The portable nature of XDS compilers makes an evaluation of sizes at constant times somewhat tricky. Different C compilers on different platforms may use various alignment algorithms, not mentioning that the base types may have different sizes.

To generate a portable code, the XDS compilers do not allow (in default mode) the call of SIZE and TSIZE functions to be used in constant expressions. E.g. one can write

  size:=SIZE(Rec);

but not

CONST Size = SIZE(Rec);

In most cases, it is not an essential restriction, since

However, both Modula-2 and Oberon-2 languages can be used in the low-level programming and it can be desirable to know a size of a type in compilation time, in spite of a potential non-portability of a program.

If the GENSIZE option is set ON, the compiler will calculate sizes of types using the value of the ALIGNMENT equation.

You have to consult with your C compiler guide to set the proper value of the equation. To prevent that inappropriate setting, the compiler generates checks in the module initialization function.

  if (sizeof(Rec) != 4) X2C_TRAP(X2C_ASSERT_TRAP);

An exception will be reported at a run-time /During the program initialization./ if the size evaluated by the XDS compiler is not equal to those evaluated by the C compiler.


Super Prev Next

Foreign language interface

Certain options are introduced to specify an interface to foreign languages, namely NOHEADER, NOEXTERN and CSTDLIB. See also the GENTYPEDEF option (Code generation) which is often used for providing a correct foreign language interface.


Super Prev Next

Disable header file

The NOHEADER option disables the generation of a header file. The option is usually specified in the foreign definition module to force the C compiler to use the original header files (See also Interfacing to C).


Super Prev Next

Disable function prototype

In some cases it may be desirable not to write a foreign definition module but to use a few C functions directly. The XDS compilers allow a C function to be declared as external:

PROCEDURE [2] / putchar(c: CHAR);

The symbol "/" marks a procedure as external. Only procedure header must be specified for an external procedure. After the declaration the external procedure can be used as usual:

  putchar('a');

A function defined as external can be implemented as macro, or have some additional specifications in its prototype. The NOEXTERN option prevents the compiler from generating a function’s prototype. In this case, a C compiler will use an original prototype (if available).


Super Prev Next

Mark C interface library

The CSTDLIB option must be set when compiling a foreign definition module, otherwise the option is ignored. For the foreign definition marked as CSTDLIB, the compiler will use angle brackets <> in the #include directive. Otherwise the compiler will use quotes.

Super Prev Next

Example

<*+ CSTDLIB *> <*+ NOHEADER *>
DEFINITION MODULE stdio;
...
END stdio.

MODULE Test;

IMPORT  stdio, MyLib;

END Test.

The import section of the module Test is translated to:

#include <stdio.h>
#include "MyLib.h"


Super Prev Next

Code presentation

In this section we describe options that do not affect the program execution, but the C code representation.


Super Prev Next

Insert line numbers

The LINENO option forces the compiler to a insert line number information into the generated C code in the form of #line directives.

Super Prev Next

Example

PROCEDURE Foo(i: INTEGER): INTEGER;
BEGIN
  i:=i*i;
  i:=i+i;
  RETURN i
END Foo;

translate to:

#line 2
static X2C_INT32 X2C_PROCLASS Foo(X2C_INT32 i)
{
  #line 4
  i=i*i;
  #line 5
  i=i+i;
  #line 6
  return i;
} /* END Foo */


Super Prev Next

Copy comments

The COMMENT option forces the compiler to copy the original file comments into the generated C code.

If the option is ON, the compiler copies comments to an appropriate place in the generated C code. Comments from an Oberon-2 module are only inserted into the C code file and not into the header file.


Super Prev Next

Insert copyright message

The COPYRIGHT equation can be used for inserting a single line comment to the very beginning of the generated code or header file. E.g., including the line

-copyright = (c) 1995 Widget Databases Ltd

to xm.cfg will cause the following line to appear at the head of the generated C text

/* (c) 1995 Widget Databases Ltd */

See also an example in Layout of a generated code.


Super Prev Next

Convert header file names

The CONVHDRNAME option forces the compiler to use a file name in the #include directive, according to the given file system. Otherwise the compiler will generate a module name postfixed by the header file extension. E.g., the include directive for the module MyLibrary will be generated under OS/2 as

#include "MYLIBRAR.H"

The option may be necessary if source text resides on a FAT partition.


Super Prev Next

Set line width

The GENWIDTH equation sets the length of a line in the generated code (by default it is equal to 78). Note: the compiler splits a line in an appropriate place, when its length exceedes the limit.