This page briefly goes over various Stony Brook Modula-2
language extensions
Additional predefined types
CARDINAL8, CARDINAL16, CARDINAL32, CARDINAL64, SHORTCARD,
LONGCARD, INTEGER8, INTEGER16, INTEGER32, INTEGER64, SHORTINT,
LONGINT, ACHAR, UCHAR, BYTEBOOL, BOOL8, WORDBOOL,
BOOL16, DWORDBOOL, BOOL32
These types are pervasive just like INTEGER and CARDINAL.
These types can also be imported from the SYSTEM module.
Doing this documents the type as an extended feature in
your source if this concerns you.
NILPROC
A new pervasive identifier NILPROC is introduced, denoting
a constant procedure value and having the type NILPROC-TYPE.
- NILPROC-TYPE is assignment compatible to every procedure
type.
- NILPROC-TYPE is compatible to every procedure type
in the comparison operations "=" and "<>".
- A call to a procedure variable having the value NILPROC
shall raise the exception "invalidLocation".
Unicode support
- The compiler supports both ASCII character and Unicode
character and string types. The definition of the CHAR
type can be either ASCII or Unicode. This is changed
with a compiler option. The compiler supports the ACHAR
and UCHAR types, which are ASCII and Unicode respectively.
- Ascii and Unicode string literals. By default a string
literal element type is of type CHAR. You can declare
a specific string literal type as ASCII or Unicode.
The compiler converts string constant to/from ASCII
and Unicode as necessary.
- The ACHR and UCHR pervasive functions complement the
existing CHR pervasive function and provide function
that return an ASCII or Unicode character type respectively.
Bitwise Operators
BAND, BOR, BXOR, BNOT, SHL, SHR, SAR, ROL, ROR
The operators perform bitwise operations. They can be
used with all cardinal and integer types.
Dynamic array types
Dynamic arrays are dynamically allocated types and are
accessed via pointer dereferencing. Dynamic arrays are
arrays of a constant number of dimensions, but the size
of the individual dimensions is unknown at compile time.
Dynamic arrays have their best use on arrays with more
than one dimension.
To specify dynamic array types, use:
POINTER TO ARRAY
OF {ARRAY OF}TypeName
Example:
TYPE
Matrix = POINTER TO ARRAY OF ARRAY OF
REAL;
Dynamic arrays are indexed the same as open array parameters,
where the lower subscript bound is assumed to be zero
and the upper bound is a runtime value that can be read
with the HIGH built-in function, and the type of the subscript
is CARDINAL.
Allocating dynamic arrays
Dynamic arrays are set by allocating a block of memory
for the object that they point to. The NEW standard
procedure. You can deallocate the memory with the DISPOSE
standard procedure. The NEW and DISPOSE procedures are
discussed in the standard procedures section of this document.
Example:
NEW(MyMatrix,
5, 7);
Extended loop control
- BREAK statement. This statement causes the current
loop to be exited. This statement is allowed with all
Modula-2 loop constructs.
- CONTINUE statement. This statement starts the next
iteration of the current loop. In FOR loops, the loop
counter is adjusts. In REPEAT, WHOLE and LOOP constructs
this statement branches to the top of the loop. This
statement is allowed with all Modula-2 loop constructs.
- Named loops. This extension is just like the Ada syntax.
You can name a specific loop and use the BREAK and CONTINUE
statements to exit a specific loop by name.
OuterLoop:
FOR i := 0 TO 9 DO
InnerLoop:
FOR j := 0 TO 9 DO
IF ... THEN
BREAK
OuterLoop; (* exits the "i" loop *)
END;
END;
END;
(* BREAK OuterLoop transfers control here *)
Support for assembly code
The compiler contains a built-in assembly language parser.
- You can embed assembly code in a Modula-2 procedure.
This is strongly discouraged.
- You can declare procedure which are implemented in
assembly code rather than Modula-2. There are two forms
of assembly procedures.
1. ASSEMBLER procedures. The compiler does all of the
tedious work of generating the procedure prologue and
epilog. You can declare local variables and the compiler
will allocate them for you. You only write the guts
of the procedure.
2. PUREASM procedures. The compiler generates nothing.
You have complete control.
Support for definition module macros.
The MACRO modifier is only allowed in DEFINITION modules
and it signals that a procedure body follows the header.
This procedure code will be generated inline at each occurrence
it is called. You are not allowed to declare nested procedures
or modules within a macro. You are allowed to declare
local types, constants and variables.
Example:
DEFINITION MODULE
MyModule;
PROCEDURE it;
PROCEDURE add(a, b : CARDINAL) : CARDINAL; MACRO;
BEGIN
RETURN a + b;
END add;
PROCEDURE it2;
END MyModule.
Procedure Attributes
Procedure attributes are a mechanism that allows you
to control and override the various aspects of how procedures
are called and linked. This allows you to interface with
any other system in existence.
Variable number of parameters.
You can call procedures, which accept a variable number
of parameters, and you can declare your own procedures,
which accept a variable number of parameters.
Variable declaration extensions
- You can change the public symbol name of any variable
for easy interfacing with other languages. Example:
VAR MyVar [PublicName] : CARDINAL;
- Initialized variables. You can declare variables with
an initial value. Global variables are initialized at
program start, and procedure variables are initialized
on entry to the procedure. Example:
VAR MyVar : CARDINAL = 23;
Enumeration syntax support to allow easy interfacing
with the C language.
Examples:
TYPE
enum = (one, two = 5, three);
In the above enumeration the ordinal value of the identifier
"three" is six.
GtkPathPriorityType
=
(
GTK_PATH_PRIO_LOWEST= 0,
GTK_PATH_PRIO_GTK= 4,
GTK_PATH_PRIO_APPLICATION = 8,
GTK_PATH_PRIO_RC= 12,
GTK_PATH_PRIO_HIGHEST= 15,
GTK_PATH_PRIO_MASK= 00fh
);
Enumeration type storage allocation adjustment.
Example:
TYPE enum = (one,
two, three) BIG;
The BIG attribute makes the type have the size of CARDINAL.
The SMALL attribute (the default) stores the type
in a byte type if possible. Note, when the SMALL
attribute is used and more than 256 enumeration identifiers
are specified, the type will have the size of CARDINAL.
Using BIG can be useful when translating C language enumerations
to Modula-2 since C enumerations are nothing more than
integer constants.
Set type storage allocation adjustment.
The BIG attribute makes the sets size the smallest
possible size, which is a multiple of the size of CARDINAL.
The SMALL attribute (the default) stores the type
in an 8-bit, or 16-bit type if possible.
When the SMALL attribute is used and the set contains
between 1 and 8 elements, the type size will be 8-bits.
When the SMALL attribute is used and the set contains
between 9 and 16 elements, the type size will be 16-bits.
If the set has 17 or more elements, the set will
be a multiple of size CARDINAL.
Record bit field support to allow easy interfacing with
C language code.
To aid in interfacing with C language code, support for
bit fields is added as an extension to the language. Bit
fields are fields within a record that occupy an amount
of space less than their declared type.
Bit fields can be declared anywhere in a record. Bit
fields are declared within a bit field block which starts
with the BITFIELDS keyword and ending with the END keyword.
Example:
TYPE
BITREC =
RECORD
before
: CARDINAL;
BITFIELDS
bitField1
: CARDINAL BY 3;
bitField2
: INTEGER BY 3;
END;
after
: CARDINAL;
END;
Signed types have a minimum bit size of 2, and unsigned
types can occupy a single bit. Bit fields are grouped
and packed via an algorithm compatible with nearly all
C compilers. This makes translation of C structures quite
straightforward.
Extensions to arrays types and variables
- HIGH function extended to all arrays
- ISO Modula-2 only allows you to pass array types to
open array parameters. We also allow you to pass a variable
whose type is the same as the element type of the open
array parameter.
- Anonymous array constants. Saves declaring and array
type which is only used in a single constant declaration.
- Open array constants. The compiler determines the
HIGH bound of the constant by the number of array elements
contained in the constant declaration.
- Array slices like the Ada syntax.
- Subscript checking suppression.The compiler will never
perform array subscript checking at compile time or
runtime for array types declared with the same lower
and upper bound. For example 0..0. A single element
array like this can be used for an array whose size
is not known at compile time and is sized dynamically
at runtime. This is only useful for single dimensional
arrays. Dynamic arrays are a better alternative for
arrays of indeterminate size.
- Arrays of Char can be assigned to each other regardless
of size.
- An open array can be the destination or source of
an assignment statement.
Type coercion
The coercion qualifier is specified by:
designator:TypeName
The coercion qualifier can be applied to any variable
designator. The data referred to by designator is
treated as if it has the type specified by TypeName. Coercion
is a method of bypassing the strong type checking of Modula-2.
Unlike ISO standard type casting type coercion has no
restrictions on usage.
Use of type coercion is dangerous and not transportable,
because it relies on knowledge of the data representation.
On the other hand, it is more efficient than other
methods of bypassing type checking, such as use of
ADDRESS types or variant records. This is because
no data is moved-you refer to the data in place.
The coercion qualifier can be used in some places where
the CAST function, imported from the SYSTEM module, cannot
be used such as the left hand side of an assignment statement.
Example:
number := buffer^[bp]:CARDINAL;
buffer^[bp]:CARDINAL := number
Type cast a literal constant
The compiler allows expression of the form. Example:
SYSTEM.CAST(CARDINAL, -8)
Taking the address of a literal constant
The compiler allows you to use the SYSTEM.ADR function
on string literals and other structured constants. String
literals are always null terminated.
Example:
CreateWindow(ADR("MyWindowClass"), ...);
Subscripting a string literal
The compiler allows you to subscript a named string literal
constant.
Character literal constants
CHR(CompileTimeConstant) is treated as a character literal
constant with the same privileges. For example the compiler
treats CHR(13) the same as 15C. This means you can use
CHR(13) with the string literal concatenation operator
(+). This also applies to the ACHR, and UCHR functions.
CONST CrLf = CHR(13) + CHR(10); (* is accepted *)
CONST CrLf = 15C + 12C; (* ISO standard *)
Extensions to MIN and MAX
These pervasive functions can accept a parameter, which
is a variable. The type of the variable is used for the
return value of these functions.
Extension to SIZE
The SIZE pervasive function has been extended to accept
open array and dynamic array variables.
The SIZE function allows you to reference record fields
when it is passed a type parameter.
Function procedure call extension
Function procedures can be called as procedures. The
function result value is discarded.
System type assignment compatibility
The parameter assignment rules of the types SYSTEM.BYTE,
SYSTEM.WORD, SYSTEM.DWORD and SYSTEM.MACHINEWORD have
been extended to all other assignment compatibility situations.
Parameter modes
The compiler supports marking VAR parameter types with
more explicit modes of operation. INOUT and OUT identifiers
are supported. INOUT parameters expect a value on input
and return a value upon exit. OUT parameters do
not expect any input value and only output a value upon
procedure return.
Using these help document code and helps, the compilers
uninitialized variable detection.
PROCEDURE p1(VAR INOUT param1 : INTEGER; VAR OUT param2
: INTEGER);
The compiler supports marking value parameters as constant,
CONST, parameters that cannot be altered within a procedure.
PROCEDURE p1(CONST
constParam : INTEGER);
BEGIN
constParam := 23; (* <== a
compilation error *)
END p1;
Importing symbols
Importing all items from a module unqualified.
FROM ModuleIdentifier IMPORT * [EXCEPT Identifier {,
Identifier}];
This has the effect of importing all exported symbols
from a given module, except those identifiers in the exclusion
list after the optional EXCEPT keyword. If an imported
symbol would collide with an already visible symbol of
the same name a compilation error is generated. In this
case you would typically use the EXCEPT keyword to exclude
the offending symbol from the import. Examples
FROM SYSTEM IMPORT
DWORD;
FROM WIN32 IMPORT * EXCEPT DWORD;
FROM WINUSER IMPORT *;
EXPORTS declaration
Provides a mechanism to control which symbols are exported
and visible outside of an executable file. This is normally
only used for DLLs and shared objects. You can export
individual symbols or entire modules.
Conditional Compilation
Conditional compilation is the compiler's ability to
decide at compile time whether or not portions of your
source program are to be compiled. Conditional compilation
can test a version tag that you define.
Version tags
You can use conditional compilation to create different
versions of a program that reside in the same source.
The version that is compiled is controlled externally,
by version tags.
Version tags are identifiers you define in one of the
following ways:
- Through the development environment version tags option
- Through the compiler version tag directive
- On the compiler command line
Predefined compiler version tags
- StonyBrook. This identifies our compiler. The Macintosh
p1 compiler uses "p1" to identify itself.
- Win32. The target operating system is Win32.
- Linux. The target operating system is Linux.
- SunOS. The target operating system is SunOS/Solaris.
- Unix. The target operating system is any flavor of
Unix. This tag is defined in addition to a specific
operating system tag.
- FlashTek. The target operating system is the X32 DOS
extender.
- Win16. The Target operating system is 16-bit Windows.
- DOS. The target operating system is DOS.
- ASCII. Modula-2 only. CHAR = ACHAR. 8-bit.
- Unicode. Modula-2 only. CHAR = UCHAR. 16-bit
- LittleEndian. The target processor uses little endian
byte order.
- BigEndian. The target processor uses big endian byte
order.
- IA32. The target processor is the IA-32 architecture.
i.e. x86, Pentium, etc...
- SPARC. The target processor is the SPARC processor.
- Bits32. The target processor is 32-bit.
- Bits16. The target processor is 16-bit.
The compile time IF statement
Conditional compilation is implemented by the compile
time IF statement. Unlike the Modula-2 IF statement, the
compile time IF operates at the token level. A compile
time IF can start between any two tokens in a program,
and can control the compilation of any string of tokens.
The format of the compile time IF statement is:
%IF CompileTimeExpression %THEN
TokenStream
{%ELSIF CompileTimeExpression %THEN
TokenStream}
[%ELSE
TokenStream]
%END
CompileTimeExpression is an expression formed by version
tags, parentheses and the operators %AND, %OR, and
%NOT. The precedence of the operators is as follows,
from highest to lowest:
%NOT
%AND
%OR
TokenStream is any stream of Modula-2 tokens.
Compile time IF's can be nested up to 16 levels. They
can be used anywhere in a source file.
The CompileTimeExpressions are evaluated as boolean expressions.
The value of a version tag is TRUE if the version
tag is defined and FALSE if it is not. The CompileTimeExpressions
are evaluated in order until one is TRUE. The TokenStream
following the TRUE expression is compiled. If none
of the expressions is TRUE, and there is an ELSE, the
TokenStream following the ELSE is compiled. All
other TokenStreams are skipped by the compiler.
Examples:
A common use of conditional compilation is to add debug
code that aids in debugging a program, but is not compiled
in the final version of the program. For example:
%IF DEBUG %THEN
WriteString('After ');
WriteCard(i, 0);
WriteString(' iterations: X = ');
WriteReal(X, 10);
WriteString(' Y = ');
WriteReal(Y, 10);
%END
Compile time IF's are not restricted to lists of statements,
as in the above example. You can also use them to
conditionalize data structures:
TYPE SymbolRecord
=
RECORD
DataType : TypePointer;
Address : %IF ThirtyTwoBit
%THEN
CARDINAL;
%ELSE
LONGCARD;
%END
NextSym : SymbolPointer;
END;
You can use the operators %AND, %OR, %NOT and parentheses
to test more complex conditions. For Example:
%IF (Win32 %OR OS2) %AND %NOT Network %THEN
...
%END
Alternate conditional compilation syntax in Modula-2
The compiler also accepts the conditional compilation
syntax used by the Macintosh p1 compiler. The syntax is
nearly identical except that it is contained in directives.
<*IF (Win32 OR OS2) AND NOT Network THEN *>
...
<*END*>
Selected listing of various SYSTEM module extensions
Machine processor count
VAR CPUCOUNT : CARDINAL;
This variable gives the number of installed processors
in the computer.
Program exit code value
VAR EXITCODE : CARDINAL;
EXITCODE contains the value passed to the HALT procedure.
Procedures installed into the termination chain may want
to check the exit value of the program.
BuildNumber Variable
VAR BuildNumber : CARDINAL;
This variable contains the build number. This value is
inserted by the linker via a linker option. The value
is user defined.
DebuggerPresent Variable
VAR DebuggerPresent : BOOLEAN;
This variable is TRUE if the program is being debugged
by the Stony Brook Debugger. Otherwise the value is FALSE.
OFFS Procedure
PROCEDURE OFFS(TypeOrVariableName.recordField{.recordField})
: ConstantValue;
OFFS returns a compile time constant value that is equal
to the offset of the specified record field from the beginning
of the record type.
Example:
TYPE
RecType =
RECORD
x, y, z : CARDINAL;
END;
CONST
yOffset = OFFS(RecType.y);
VAR
v : RecType;
BEGIN
v.x := OFFS(v.z);
Unreferenced parameters.
PROCEDURE UNREFERENCED_PARAMETER(AnyParameter);
The compiler generates a warning when a parameter of
a procedure is not referenced within that procedure. In
some instances it is necessary to have an unreferenced
parameter within a procedure. Operating system call
back procedures are such an example. The UNREFERENCED_PARAMETER
procedure can be used to suppress warnings on unreferenced
parameters.
SOURCEFILE Variable
This identifier represents a string literal whose value
is the filename of the source file being compiled.
SOURCELINE Variable
This identifier represents a numeric constant whose value
is the source line number where this instance of the identifier
is used.
ASSERT Statement
This is a statement that checks a BOOLEAN expression
for TRUE and if not raises an ASSERT exception. The assert
exception will identify the module and line number of
the failed ASSERT statement. ASSERT will generate this
test code, if and only if, the version tag M2ASSERT is
set, otherwise no code for the ASSERT statement is generated.
The format of the ASSERT statement is:
ASSERT(BooleanExpression);
Example:
Assume the M2ASSERT version tag is set.
PROCEDURE foo(ptr
: ADDRESS);
BEGIN
ASSERT(ptr <> NIL);
(* will never get here if ptr = NIL *)
(* think of the ASSERT statement like this
%IF M2ASSERT %THEN
IF ptr = NIL THEN
RAISE(...);
END;
%END
*)
END foo;
ISASSERT function
PROCEDURE ISASSERT() : BOOLEAN;
This is a function that returns a BOOLEAN value. You
can use this to trap ASSERT exceptions. It returns TRUE
if the current exception is an ASSERT exception.
EXCEPTADR Procedure
PROCEDURE EXCEPTADR() : ADDRESS;
This is a function that returns the address where the
current exception occurred. It will return NIL if there
is no exception.
EXCEPT_INFO Procedure
PROCEDURE EXCEPT_INFOADR(VAR
OUT addr : ADDRESS;
VAR OUT lineNumber
: CARDINAL;
VAR OUT moduleName
: ARRAY OF ACHAR);
This is a function that returns information about the
current exception. The procedure will return NIL in addr
if there is no exception. If the exception occurred because
of a runtime check then the lineNumber and moduleName
parameters will return the location of the checking error.
If the exception did not result from a runtime check the
lineNumber will return 0, and modulename will return an
empty string.
SetUnhandledExceptionProc Procedure
PROCEDURE SetUnhandledExceptionProc(unhandled : PROC);
This procedure lets you set a procedure that will be
called when an exception is unhandled.
AttachDebugger procedure (Win32 only)
TYPE
AttachDebuggerOpt =
(
DoNotAttach, (* do not
attach, the default *)
AttachExternal,(* attach on EXCEPTIONS.sysException
only. this will primarily be access violations *)
AttachAll (* attach on all raised
exceptions. this includes ALL Modula-2 exceptions *)
);
PROCEDURE AttachDebugger(opt : AttachOptions);
Use this procedure to enable or disable just in time
debugging support. You usually put this call as the first
line of code in a program.
For example, if you use AttachExternal, then when you
program gets an access violation or other system exception
the debugger will be attached to the program allowing
you to start debugging at the point of the exception.
OutputDebugMessage procedure
PROCEDURE OutputDebugMessage(str : ARRAY OF CHAR);
This procedure actually does nothing except that our
debugger for Win32 and Unix systems will trap calls to
this procedure and display the passed parameter in the
debug messages window.
EnableCallTrace procedure
PROCEDURE EnableCallTrace;
By calling this procedure you enable the runtime system
to perform a call trace when an exception of any kind
is raised. If the exception goes unhandled then the call
trace will be output to disk. If the exception is handled
then the information is lost.
OutputCallTrace procedure
PROCEDURE OutputCallTrace;
This procedure is only used with EnableCallTrace. If
you want to handle an exception, but still want the call
trace output to disk then you should call this procedure
inside your exception handler. For example this allows
you to handle all exceptions, hopefully terminating gracefully,
and still getting a call trace which may help you debug
the problem.
TrapAccessViolations procedure (Unix systems only)
PROCEDURE TrapAccessViolations;
This procedure is used in shared objects to enable trapping
access violations. Main programs automatically trap access
violations. Access violations consist of the SIGSEGV,
SIGBUS and SIGILL signals. These signals are global to
an entire process, therefore our runtime system does not
assume it can possess these signals when running in a
shared object. Trapping an access violation means the
violation is converted to a native language exception.
Note that it is still possible for code to override our
runtime system signal handlers for these signals. This
can happened in main programs and shared objects. When
this occurs the violation will not be trapped by our runtime
system.
VA_START, VA_ARG procedures
PROCEDURE VA_START(VAR
OUT addr : ADDRESS);
PROCEDURE VA_ARG(VAR INOUT addr : ADDRESS;
TypeIdentifier) : ADDRESS;
These procedures are used to support accessing the parameters
of a procedure, which accepts a variable number of parameters
(the VARIABLE procedure attribute). You use VA_START to
get the address of the first variable argument. You then
use VA_ARG for each argument passing the address VA_START
initialized. The second parameter of the VA_ARG function
is the type identifier of the argument you are about to
read. The function returns a pointer to the data. The
function also updates the parameter address, addr.
PROCEDURE example(format
: ARRAY OF CHAR)
[RightToleft, Leaves, Variable];
TYPE
Pointer = POINTER TO CARDINAL;
(*all pointers are the same size*)
VAR
addr : ADDRESS;
ptrC : POINTER TO CARDINAL;
ptrCh : POINTER TO CHAR;
BEGIN
VA_START(addr);
ptrC := VA_ARG(addr, CARDINAL);
(* use ptrC *)
ptrCh := VA_ARG(addr, Pointer);
(* use ptrCh *)
END example;
CLONE procedure
PROCEDURE CLONE(VAR newObject : <some_class_type>;
sourceObject : <some_class_type>);
This procedure creates an exact copy of the object referenced
by sourceObject and stores a reference to the new object
in the variable denoted by newObject.
FUNC Keyword
This keyword allows you call a function procedure as
a procedure, thus ignoring the result. No Warning will
be generated in extended syntax mode, and no error generated
in ISO mode.
Example:
BEGIN
returnVal := MyFunction(param1, param2);
FUNC MyFunction(param1, param2);
END;
FIXME Procedure
PROCEDURE FIXME(str : ARRAY OF CHAR);
This "procedure" allows you cause a warning
to be generated in the source file at the source location
with a string value passed to this function. No code is
generated by the use of this procedure. This warning can
never be suppressed with compiler options. You can use
this feature to document something that needs addressing
in your code and by having a warning generated you can
use the mechanism's built into the development system
for finding warnings errors in source files.
SWAPENDIAN Procedure
PROCEDURE SWAPENDIAN(IntegerType) : SameIntegerType;
PROCEDURE SWAPENDIAN(VAR INOUT : IntegerOrRealType);
This is a function procedure that allows converting a
number between little and big endian byte ordering format.
This only has use for code that stores data on disk, or
transmits data across a network, that is used on computers
that have different natural data formats. This procedure
can be called as a function or a procedure. When call
as a function only integer types can be used. When called
as a procedure you can pass both integer types and floating
point types.
16-bit, 32-bit and 64-bit INTEGER and CARDINAL types
can be used with this function. REAL and LONGREAL can
be used.
Examples:
VAR
card32 : CARDINAL32;
card16 : CARDINAL16;
r : REAL;
BEGIN
...
card32 := SWAPENDIAN(card32);
card16 := SWAPENDIAN(card16);
SWAPENDIAN(card32);
SWAPENDIAN(r);
BIGENDIAN, LITTLEENDIAN Procedures
PROCEDURE BIGENDIAN(IntegerType) : SameIntegerType;
PROCEDURE BIGENDIAN(VAR INOUT : IntegerOrRealType);
PROCEDURE LITTLEENDIAN(IntegerType) : SameIntegerType;
PROCEDURE LITTLEENDIAN(VAR INOUT : IntegerOrRealType);
These functions are like SWAPEENDIAN except they always
result in a specific endian format. They assume the input
data is in the native format for the target processor
and operating system. Therefore these procedures may,
or may not, generate code. For example LITTLEENDIAN will
never generate code when targeting an IA-32 processor.
It will however generate code if the target is a SPARC
processor.
LITTLEENDIAN is equivalent to
%IF BigEndian
%THEN
SWAPENDIAN(data);
%END
Multi-processing support functions
The compiler implements various intrinsic functions useful
in multiprocessing. These functions generate inline machine
code rather than a procedure call. These functions perform
atomic operations in a multiprocessing environment. This
means that the processor executing these functions has
exclusive access to the data being operated on.
Atomic compare and exchange
PROCEDURE ATOMIC_CMPXCHG(VAR INOUT data : SomeType; compare,
source : SomeType) : SomeType;
where SomeType can be CARDINAL, INTEGER, DWORD or ADDRESS.
Remember that pointers are compatible with ADDRESS parameter
types.
This procedure compares the value in data with the value
in compare and if equal, then the value in source is assigned
to data. The function result is the value previously held
in data. Only the variable data is atomically accessed.
This function makes the new value written in data visible
to other processors before returning.
Atomic exchange
PROCEDURE ATOMIC_XCHG(VAR INOUT data : SomeType; source
: SomeType) : SomeType;
where SomeType can be CARDINAL, INTEGER, DWORD or ADDRESS.
Remember that pointers are compatible with ADDRESS parameter
types.
This procedure performs an atomic exchange of the value
in data with the value in source. The function result
is the value previously held in data. Only the variable
data is atomically accessed. This function makes the new
value written in data visible to other processors before
returning. This operation may be used as a function or
as a statement ignoring the return value.
Note: The return value may not be the same as the value
in data, because another processor may have altered the
value one processor cycle after this function alters the
value.
Atomic add
PROCEDURE ATOMIC_ADD(VAR INOUT data : SomeType; constantValue
: INTEGER) : SomeType;
where SomeType can be CARDINAL or INTEGER.
constantValue must be in the range
-128..127 for IA32
-4096..4095 for SPARC
This procedure adds the value in constantValue with the
value in data. Positive numbers perform addition and negative
numbers perform subtraction. The function result is the
result of the addition/subtraction to data. Only the variable
data is atomically accessed. This function makes the new
value written in data visible to other processors before
returning. This operation may be used as a function or
as a statement ignoring the return value.
Memory fence/barrier
PROCEDURE MEMORY_FENCE;
Note: This procedure is not necessary and does not perform
any actions on IA-32 architecture processors (x86 processor
family) since these processors support strong write ordering.
Note: You do not need to use a memory fence with any
synchronization objects supported by the runtime library
or the operating system, as they will take necessary actions.
This procedure generates a memory fence or barrier, and
is necessary on processors that do not guarantee memory
access order.
The procedure guarantees that all subsequent loads or
stores will not access memory until after all previous
loads and stores have accessed memory, as observed by
other processors. The following pseudo code describes
this further
1) <Acquire lock>
2) MEMORY_FENCE
3) critical section code
4) MEMORY_FENCE
5) <release lock>
The first memory fence stops the processor from looking
ahead and pre-fetching any data used in the critical section.
The second memory fence makes sure that any data written
in the critical section is made visible to other processors
before the write that releases the software lock. The
memory fence is generally only used when implementing
spinlocks. |