This report describes Paradigm/SP, an object-oriented parallel programming language that supports the development and use of generic parallel algorithms. In Paradigm/SP, generic parallel algorithms are represented as extensible modules that encapsulate module-bound classes and methods. Such modules can be extended with sequential domain-specific code in order to derive particular parallel applications. Module extension is achieved through module embedding, class overriding and method overriding. The Paradigm/SP language also incorporates standard means for secure parallel message-passing programming, such as send, receive, for-all, and parallel statements, and channel types.
Module extension is a code reuse mechanism that enables building of new modules from existing ones. An extensible module encapsulates public and private (1) classes and other types, (2) procedure and function methods, (3) global variables, and (4) statements for module initialization. Module extension consists of module embedding, class overriding, and method overriding. Existing modules can be embedded in a newly declared module. The embedding module can declare new components in addition to those inherited from its embedded modules. An inherited class definition can be overridden (i.e., re-declared) with an extended class definition. Similarly, an inherited method implementation can be overridden with a new method implementation.
Parallel programming is supported with deterministic statements for parallel processes and synchronous message communication. A parallel statement denotes parallel execution of a fixed number of statements. A forall statement denotes parallel execution of the same statement by a dynamic number of processes. Recursive procedures may be combined with parallel and forall statements to define recursive parallel processes. Parallel processes communicate by sending typed message through channels created dynamically. Restrictions on the use of variables enable a single-pass compiler to check that parallel processes are disjoint.
The syntax of Paradigm/SP is specified in Section 13. Individual sections of this report do not contain syntax definitions but incorporate appropriate links to Section 13 instead.
A prototype implementation of the Paradigm/SP language has been developed. The implementation consists of a compiler that generates abstract code and a loader and an interpreter for this abstract code. All source files of the prototype implementation (currently in Turbo Pascal) are freely available.
Example: Principal parts of an extensible module.
module M0;Modules are extensible, i.e., a module can be declared as an extension of one or more base modules.
const
{ ... constant declarations... }
type
{ ... type declarations... }
var
exported*: typeIdent...;
private: typeIdent...;
procedure dynamic*;
begin {exported method} end;
procedure static;
begin {private method} end;
begin
{ ... module initialization ... }
end.
Example: Module M1 is a (direct) extension of its (direct) base module M0.
module M1(M0);An extended module M1 inherits all components of its base module M0. Only identifiers that are exported by M0 are visible in the extension M1; such identifiers are re-exported by M1. Besides, an extended module may declare new identifiers in addition to those inherited from its base modules. Newly declared identifiers must be different from identifiers that are exported by base modules but can be the same as their private identifiers.
{ ... new declarations... }
var
private : typeIdent;
procedure dynamic*;
begin { overrides M0.dynamic } end;
procedure static;
begin { new private method} end;
begin
{ ... module initialization ... }
end.
Different modules M1 and M2 that belong to an application can import the same module M0. The imported module M0 is shared between modules M1 and M2. Any change of M0 by, say, M1 is visible for M2 as well.
Alternatively, if module M0 is extended by modules M1 and M2, each of these modules will have M0 as its proper part. Now, M1 and M2 incorporate separate instancesof M0. Therefore, M1 may change components inherited from M0, but these changes do not affect the same components inherited by M2. Besides, M1 and M2 can make different extensions of the same class inherited from M0. Likewise, M1 and M2 can have different methods override the same method inherited from M0.
Module import and extension can be mixed, if necessary in order to implement shares-a and contains-a relationships. In particular, a module M1 can import module M0 and, at the same, extend module M0. In such a case, M1 incorporates a separate instance of its base module M0 and also refers to the shared instance of the imported module M0. Note that the qualified identifier M0.x stands for an imported entity x, while the identifier x always stands for an inherited one.
A module can be directly executed for either testing the module, or because that module is intended to be used as a main module, or main program in traditional terms. This approach eliminates the need for a special linguistic construct for main programs. A benefit is that a main module (i.e., a main program) is as extensible and adaptable as any other module.
A client module C0 specifies a list of imported modules: module C0; import M0, M1; ... end. The client, C0, can refer to an entity x that is exported by M0 only through a qualified identifier, M0.x. At run time, the implementation executes imported modules before the execution of the client. Since imported modules are shared between client modules, the implementation loads only one instance of each imported module and this copy is shared by all of its clients.
The execution of a module body is preceded by the execution of the bodies of its base modules, if any. This rule ensures proper module initialization.
A class that is exported by a base module M0 can be re-declared in an extension M1 of M0. A class definition in M1 extends the definition inherited from M0; the extended definition comprises all fields originally specified in M0 and, in addition, the fields specified in M1. The extended type definition overrides the type definition inherited from M0. The class originally declared in the base module M0 is dynamically bound to the definition extended by M1.
Example: Consider class C declared in M0 and extended in M1, and an object exported by M0. Although the object is originally declared in M0, when inherited by M1 it contains all fields that belong to the extended definition of class C.
module M0;
type C* = class a*: integer;end;
var object*: C;
begin object.a := 0; end.module M1(M0);
type C* = class b*: integer;end;
begin object.b := 0; end.
A method itself can contain declarations of other entities (such as constants, types, variables, and other methods) that are private for the enclosing method and cannot be exported.
A method identifier that is declared in a module can be marked for export with either a restricted mark "*" or an unrestricted mark "-".
A restricted method is a method exported with a restricted mark, "*".
An unrestricted method is one of the following:
1) a method exported with an unrestricted mark, "-", orA restricted method cannot have implicit parameters (see definition of implicit parameter in Section 8.2) and cannot invoke unrestricted methods.
2) a private method that invokes an unrestricted method, or both.
An unrestricted method can have implicit parameters and invoke unrestricted methods. An unrestricted method can be invoked only in an unrestricted statements (see definition of unrestricted statements in Section 11.5).
A method identifier exported by a base module M0 can be re-declared in its extension M1, provided that its heading in M1 is the same as in M0 (in particular the export mark, "*" or "-", must be the same). The newly declared method body overrides the method body inherited from the base module. The body of a method P that is inherited from M0 and overridden in M1 is still available in M1 and can be invoked through the designators ^P.
A method does not belong to a class but to a module (or, alternatively, to another method). Objects that belong to the class can be passed as parameters to the method.
Examples:
module M0;
type account* = class
balance*: real;
end;
var total*: real;
procedure open*( var a: account);
begin a.balance := 0; end;proceduretransact* (var a: account; amt: real);
begin a.balance := a.balance + amt;
total := total + amt;
end;procedure close* (var a: account);
begin transact (a, - a.balance) end;begin {initialization of M0} total := 0; end.
---
module M1(M0);
type account* = class
number*: integer;
end;
var numAccnts*: integer;procedure open* (var a: account);
begin ^open(a);
numAccnts := numAccnts + 1;
a.number := numAccnts;
end;procedure close* (var a: account);
begin ^close(a);
numAccnts := numAccnts – 1;
end;begin {initialization of M1}
numAccnts := 0;
end.---
module client;
import M1;
var account: M1.account;
begin {client}
M1.open(a); M1.transact(a, 100);
M1.transact(a, -50); M1.close(a);
end.
The evaluation or execution of a command is called a process. A structured process is a sequential or parallel composition of processes. The components of a parallel composition are called parallel processes. They proceed independently at unpredictable speeds until all of them have terminated.
In a program text an entire variable is a syntactic entity that has an identifier, a type, and a scope.
During program execution a block is activated when a process evaluates a function designator or executes a procedure statement or module. Every activation of a block B creates a new instance of every variable that is local to B. When an activation terminates, the corresponding variable instances cease to exist.
During recursive and parallel activations of a block, multiple instances of the local variables exist. Each variable instance is a dynamic entity that has a location, a current value, and a finite lifetime in memory.
The distinction between a variable as a syntactic entity in the program text and a class of dynamic entities in memory is usually clear from the context. Where it is necessary, this report distinguishes between syntactic variables and variable instances.
Parallel processes are said to be disjoint if they satisfy the following condition: Any variable instance that is assigned a value by one of the processes is not accessed by any of the other processes. In other words, any variable instance that is accessed by more than one process is not assigned a value by any of the processes.
Standard scalar types are integer, real, char, boolean. User-defined types include enumerated types, array types, channel types, class types. Class types are discussed in Section 4. A standard string type is persented in Section 12.
Examples: Type declarations.
type
vector = class x, y: real end ;
v = vector;
body = class m: real; r, v, f: vector end;
system = array [1..n] of body;
channel = *(body);
net = array [0..p] of channel;
mixed = *(body, integer);
two = array [0..1] of mixed;
four = array [0..1] of two;
Examples: Channel types.
*(body)
*(body, integer)
varA variable context is associated with each command C. This context consists of two sets of entire variables called the target and expression variables of C. If the process denoted by C may assign a value to an entire variable v (or one of its components), then v is a target variable of C. If the process may use the value of v (or one of its components) as an operand, then v is an expression variable of C.
inp, out: channel;
c: net;
a: system;
ai, aj: body;
left: mixed;
top: four;
i, j, k: integer;
A function block cannot use formal variable parameters or implicit variable parameters.
A recursive procedure or function block cannot use implicit parameters.
Examples:
inp
c[0]
top[i,j]
Examples:
out
c[k-1]
Example:
left = top[i,j]
The open statement is executed by creating a new channel and assigning the corresponding reference to the channel variable v. The channel reference is of the same type as the channel variable.
The abbreviation open(v1, v2, ... , vn) is equivalent to open(v1); open(v2, ... , vn).
Examples:
open(c[k]);
open(inp, out);
The receive statement receive(c, v) denotes input of the value of a variable v through the channel denoted by an expression c. The expression c must be of a channel type T, and the type of the variable v must be a message type of T.
The send and receive operations defined by the above statements are said to match if they satisfy the following conditions:
The following communication errors can occur:
The abbreviation receive(c, v1, v2, ... , vn) is equivalent to receive(c,v1); receive(c, v2, ... , vn).
Examples:
send(out, ai);
receive(inp, aj);
send(top[i,j], 2, ai);
Example:
left := top[i,j];
Restriction: The restricted actual parameters of a procedure statement must be distinct entire variables (or components of such variables). Similarly, the actual parameters of an external procedure statement must be distinct entire variables.
A procedure statement cannot occur in the statement part of a function block. This rule also applies to a standard or external procedure statement .
The effect of a parallel statement is to execute the process statements as parallel processes until all of them have terminated.
Example:
parallelRestrictions: In a parallel statement:
source(a, c[0]); sink(a, c[p]) |
forall k := 1 to p do
node(k, c[k-1], c[k])
end
1. A target variable of one process statement cannot be a target or expression variable of another process statement.
2. A process statement cannot invoke an unrestricted method (see Section 5).
The index variable declaration i := e1 to e2 introduces the index variable i which is local to the element statement S.
A forall statement is executed as follows:
1. Use target variables.Examples:
2. Invoke unrestricted methods (see Section 5).
forall k := 1 to p do
node(k, c[k-1], c[k])forall i := 0 to 1 do
forall j := 0 to 1 do
quadtree(i, j, top[i,j])
Restricted statements must satisfy the rules labeled as restrictions in this report. These rules restrict the use of entire variables in procedure statements, parallel statements, and forall statements to make it possible to check the disjointness of parallel processes during single-pass compilation (see Sections 11.2, 11.3, and 11.4).
The same rules do not apply to unrestricted statements. Consequently, the programmer must prove that each unrestricted statement preserves the disjointness of parallel processes; otherwise, the semantics of unrestricted statements are beyond the scope of this report.
Examples: Unrestricted statements.
[sic] { i <> j }swap(a[i], a[j]);[sic] { i <> j }a[i] := ai|a[j] := aj;
[sic] { disjoint elements a[i] }
forall i := 1 to n do a[i] := ai;
Example:
assume i <> j;
The standard text file input is the only input file. The file identifier is omitted from eof and eoln function designators and read and readln statements. The input file is an implicit value parameter of the eof and eoln functions and is an implicit variable parameter of the read and readln procedures (see Section 8.2).
The standard text file output is the only output file. The file identifier is omitted from write and writeln statements. The output file is an implicit variable parameter of the write and writeln procedures (see Section 8.2).
The following is an alphabetical list of all standard identifiers:
Module =
ModuleHeading
[ "import" ImportedModuleList
";" ]
Block "." .
ModuleHeading =
"module" ModuleIdentifier [ "(" BaseModuleList
")" ] ";" .
ModuleList =
[ Identifier ":=" ] Identifier
[ "," [ Identifier ":=" ] Identifier ] .
Block =
DeclarationPart StatementPart
.
DeclarationPart =
[ ConstantDeclarations
]
[ TypeDeclarations
]
[ VariableDeclarations
]
MethodDeclarations
.
ConstantDeclarations =
"const" ConstantDeclaration
";"
[ ConstantDeclaration
";" ]* .
ConstantDeclaration =
ConstantIdentifier [ ExportMark
] "=" Constant .
ExportMark = "*" .
Constant =
[ Sign ] UnsignedConstant
.
Sign =
"+" | "-" .
UnsignedConstant =
ConstantQualIdent
|
UnsignedOrdinal |
UnsignedReal |
CharacterString .
QualIdent =
[ ModuleIdentifier "." ] Identifier .
UnsignedOrdinal =
CharacterConstant
|
UnsignedInteger .
CharacterConstant =
"'" CharElement "'" .
CharElement =
GraphicCharacter |
ApostropheImmage
.
ApostropheImmage =
"''" .
UnsignedInteger =
DigitSequence .
DigitSequence =
Digit [ Digit ]* .
UnsignedReal =
IntegerPart RealOption
.
IntegerPart =
DigitSequence .
RealOption =
"." FractionalPart
[ ScalingPart ] |
ScalingPart .
FractionalPart =
DigitSequence .
ScalingPart =
Radix ScaleFactor
.
Radix =
"e" | "E" .
ScaleFactor =
[ Sign ] DigitSequence
.
CharacterString =
"'" StringElements
"'" .
StringElements =
CharElement [ CharElement
]* .
TypeDeclarations =
"type" TypeDeclaration
";" [ TypeDeclaration ";" ]* .
TypeDeclaration =
TypeIdentifier [ ExportMark
] "=" NewType | ExistingType
.
ExistingType =
TypeQualIdent
.
NewType =
EnumeratedType |
ArrayType |
ClassType |
ChannelType .
EnumeratedType =
"(" NewConstantList
")" .
NewConstantList =
ConstantIdentifier [ "," NewConstantList
] .
ArrayType =
"array" IndexRange
"of" TypeQualIdent .
IndexRange =
"[" OrdinalConstant
".." OrdinalConstant "]" .
ClassType =
".." | "class" FieldList
"end" .
FieldList =
ClassSection [ ";" ClassSection]*
[ ";" ] .
ClassSection =
FieldIdentifier [ ExportMark
] SectionTail .
SectionTail =
"," ClassSection | ":"
TypeQualIdent
.
ChannelType =
"*" "(" MessageTypeList
")" .
MessageTypeList =
MessageTypeQualIdent
[ "," MessageTypeQualIdent ]* .
VariableDeclarations =
"var" VariableDeclaration
";" [ VariableDeclaration ";" ]* .
VariableDeclaration =
VariableIdentifier [ ExportMark
] VariableTail .
VariableTail =
"," VariableDeclaration
|
":" TypeQualIdent
.
MethodDeclarations =
[ FunctionDeclaration
";" | ProcedureDeclaration ";" ]* .
FunctionDeclaration =
FunctionHeading ";"
MethodBlock
.
FunctionHeading =
"function" FunctionIdentifier [ MethodExportMark
]
FormalParameterPart
":" TypeQualIdent .
MethodExportMark =
"*" | "-" .
FormalParameterPart =
[ FormalParameterList
] .
FormalParameterList =
"(" FormalParameters
")" .
FormalParameters =
FormalParameterSection
[ ";" FormalParameterSection ]* .
FormalParameterSection =
[ "var" ] VariableDeclaration
.
VariableDeclaration =
VariableIdentifier [ ExportMark
] VariableTail .
VariableTail =
"," VariableDeclaration
|
":" TypeQualIdent
.
ProcedureDeclaration =
ProcedureHeading
";" MethodBlock .
ProcedureHeading =
"procedure" ProcedureIdentifier [
MethodExportMark
] FormalParameterPart .
StatementPart =
CompoundStatement
.
Expression =
SimpleExpression
[ RelationalOperator SimpleExpression
] .
RelationalOperator =
"<" | "=" | ">" | "<=" | "<>" | ">=" .
SimpleExpression =
[ Sign ] Term
[ AddingOperator Term
]* .
AddingOperator =
"+" | "-" | "or" .
Term =
Factor [ MultiplyingOperatorFactor
]* .
MultiplyingOperator =
"*" | "/" | "div" | "mod" | "and"
.
Factor =
UnsignedConstant
|
QualifiedVariableAccess
|
FunctionDesignator
|
"(" Expression ")" |
"not" Factor .
QualifiedVariableAccess =
QualIdent [ ComponentSelector
] * .
ComponentSelector =
IndexedSelector |
FieldSelector .
IndexedSelector =
"[" IndexExpressions
"]" .
IndexExpressions =
OrdinalExpression
[ "," OrdinalEpression ]* .
FieldSelector =
"." FieldIdentifier .
FunctionDesignator =
FunctionIdent ActualParameterPart
|
StandardFunctionDesignator
.
FunctionIdent =
OverloadedIdent |
QualIdent
.
OverloadedIdent =
" ^ " Identifier .
ActualParameterPart =
[ ActualParameterList
] .
ActualParameterList =
"(" ActualParameters
")" .
ActualParameters =
[ ActualParameters
"," ] ActualParameter .
ActualParameter =
Expression |
VariableAccess .
VariableAccess =
EntireVariableAccess
[ ComponentSelector ]* .
EntireVariableAccess =
VariableIdentifier .
StandardFunctionDesignator
=
FileFunctionDesignator
|
MathFunctionDesignator
.
FileFunctionDesignator =
"eof" | "eoln" .
MathFunctionDesignator =
MathFunctionIdentifier
"(" Expression ")" .
MathFunctionIdentifier =
"abs" | "arctan" | "chr" |
"cos" | "exp" | "ln" |
"odd" | "ord" | "pred" | "round"
| "sin" | "sqr" |
"sqrt" | "succ" | "trunc" |
"random".
Statement =
EmptyStatement |
AssignmentStatement
|
ProcedureStatement|
ExternalProcedureStatement
|
IfStatement |
WhileStatement |
RepeatStatement |
ForStatement |
CaseStatement |
CompoundStatement
|
ParallelStatement
|
ForallStatement |
UnrestrictedStatement
|
AssumeStatement .
EmptyStatement = .
AssignmentStatement =
LeftPart ":=" Expression
.
LeftPart =
FunctionVariable
|
VariableAccess .
FunctionVariable =
FunctionIdentifier .
ProcedureStatement =
ProcedureIdentifier ActualParameterPart
|
StandardProcedureStatement
.
StandardProcedureStatement
=
ReadStatement |
WriteStatement |
OpenStatement |
ReceiveStatement
|
SendStatement .
ReadStatement =
"read" ReadParameterList
|
"readln" [ ReadParameterList
] .
ReadParameterList =
"(" ReadParameters
")" .
ReadParameters =
ReadParameter [ ","
ReadParameter
]* .
ReadParameter =
VariableAccess .
WriteStatement =
"write" WriteParameterList
|
"writeln" [ WriteParameterList
] .
WriteParameterList =
"(" WriteParameters
")" .
WriteParameters =
WriteParameter [ ","
WriteParameter
]* .
WriteParameter =
Expression WriteOptionWriteOption
.
WriteOption =
[ ":" Expression ] .
OpenStatement =
"open" "(" OpenParameters
")" .
OpenParameters =
OpenParameter [ ","
OpenParameter
]* .
OpenParameter =
ChannelVariableAccess
.
ReceiveStatement =
"receive" "(" ReceiveParameters
")" .
ReceiveParameters =
ChannelExpression
"," InputVariableList .
InputVariableList =
InputVariableAccess
[ "," InputVariableAccess ]* .
SendStatement =
"send" "(" SendParameters
")" .
SendParameters =
ChannelExpression
"," OutputExpressionList .
OutputExpressionList =
OutputExpression
[ "," OutputExpression ]* .
ExternalProcedureStatement
=
ExternalIdent ActualParameterPart
.
ExternalIdent =
OverloadedIdent |
QualIdent
.
IfStatement =
"if" Expression
"then" Statement [ "else" Statement
] .
WhileStatement =
"while" Expression
"do" Statement .
RepeatStatement =
"repeat" StatementSequence
"until" Expression .
StatementSequence =
Statement
[ ";" Statement ]* .
ForStatement =
ForClause ForOption
.
ForClause =
"for" EntireVariableAccess
":=" Expression .
ForOption =
UpClause | DownClause
.
UpClause =
"to" Expression
"do" Statement .
DownClause =
"downto" Expression
"do" Statement .
CaseStatement =
"case" OrdinalExpression
"of" CaseList "end" .
CaseList =
CaseListElement [
";" CaseListElement ]* . [ ";" ] .
CaseListElement =
CaseConstantList
":" Statement .
CaseConstantList =
CaseConstant [ "," CaseConstant
]* .
CaseConstant =
OrdinalConstant .
CompoundStatement =
"begin" StatementSequence"end"
.
ParallelStatement =
"parallel" ProcessStatementList
"end" .
ProcessStatementList =
ProcessStatement
[ "|" ProcessStatement ]* .
ProcessStatement =
StatementSequence.
ForallStatement =
"forall" IndexVariableDeclaration
"do" Statement .
IndexVariableDeclaration =
VariableIdentifier ":=" ProcessIndexRange
.
ProcessIndexRange =
Expression "to"
Expression
.
UnrestrictedStatement =
SicClause Statement
.
SicClause =
"[" "sic" "]" .
AssumeStatement =
"assume" Expression
.
Radenski, A. (1998) The Paradigm/SP Implementation Notes. Computer Science Department, Winston-Salem State University, Winston-Salem, NC.
Last updated: February 2000.