Skip to main content

All change .. not quite



With the recent release of PM 0.4 and the positive reception to my PM presentation at CIUK2023, it seems like a good time to bring back the PM blog after a long hiatus. Another good reason for its resurrection is that I feel that I now have built the basic semantics of the language into something like a coherent whole, giving me something concrete to write about.

There have been a few major changes to the language since my last blog entry. The main syntactic change has been the shift from keyword-delimited control statements to curly-brackets. This is not a statement on my part as to the merits of the two approaches, I am generally agnostic in this debate which can border on the religious. It was simply that with the way that the language was developing, the keyword approach was getting cumbersome – frequently used constructs were taking up far to much space and impeding readability. PM now uses curly brackets to terminate statements and semicolons to separate (and optionally terminate) them.


if x<0 { 

       print("x is negative");

       x=-x

}


The semicolons can be omitted if the next statement starts on a new line:


if x<0 { 

       print("x is negative");

       x=-x

}


Single-statement blocks can start with a semicolon, rather than being enclosed in braces:

if x<0 : x=-x


A more fundamental change has been made to the language semantics. In previous versions, operators or procedures could be either mapping, returning a separate value for each strand, or reducing, generating a single uniform value for all strands (a strand is similar-ish to a green thread). However, this proved to be far too restrictive and it occurred to me that I had somehow managed to recreate a feature of early microcomputer BASIC, in which the nature of a returned value was linked to the syntax of the call (anybody else remember STR$ … ?). The obvious way forward was to bring value uniformity (or lack of it) out of operator/call syntax and into the type system. This has now built up into a much richer set of value modes (perhaps not the best name - I am working on that).

partial

The object is defined in some of the strands in the current parallel context, but not necessarily all. The object may have different values in different strands.

coherent

The object is defined in all strands in the current parallel context with values that may differ between strands but are synchronised with other stands in that context.

chan

The object is defined in all strands in the current parallel context, with values that may differ between strands. Values may be communicated between strands in the current parallel context.

uniform

The object is defined in all strands in the current parallel context and has exactly the same value in all of these strands.

shared

The object is defined in the directly enclosing parallel context.


The idea of value uniformity as a type modifier is cropping up in other languages, such as ISPC. PM takes this a bit further by incorporating concepts used in MPI and OpenMP programming (frequently only enforced by program design.) I am still debating whether values local to a node, but not to each strand, need to have their own mode. This does make sense in many ways, but could conflict my aspiration of placing the association between nodes and actual hardware fully and flexibly under programmer control. I am working these ideas through, so watch this space (and feel free to comment…).Anyway, that is enough for now. I will explore these topics in future blogs as I work them through.

Comments

Popular posts from this blog

Data in, data out

When a program is running over multiple processors or processing cores then it is important to be able to map where data are flowing. One way in which data flows can be translated into programming language semantics is though argument passing to subroutines. In the absence of global variables, data transfer to and from a subroutine straightforwardly can be directly related to communication to and from another processor. In PM, the emphasis is on data parallelism through the for statement. for index in array do index=process(index) endfor      So what are the rules governing data flow into and out of this statement? These are primarily based around variable locality. x:= 1 for index in array do y:=func(index) index=process(x,y) endfor Here x is relatively global to the for statement and y is local to it (a variables scope only extends to the end of the statement block in which it is defined).  There are two ways t...

Compile time, run time, coffee time

[ Please note that in the following discussion I will be using PM 0.5 notation, which is slightly different to PM 0.4 and also in some flux as PM 0.5 is still in development. ]   Many programming languages (and most newly developed ones) include some form of compile-time programming, ranging from simple preprocessors ( #define , #if in C) to fully blown macro systems capable of re-writing the abstract syntax tree during compilation (Rust, Julia, etc .). In line with its philosophy of keeping things as simple as possible, but not simpler, PM takes a middle road with compile-time programming supported primarily through the type system. There is nothing too radical here – this is not an area where the language aims to break new ground.  The PM approach centres around the use of compile-time types. Many languages use special types to denote literal values and PM follows this trend. Literal integers, reals, strings and Booleans each have their own types: literal(int) , litera...

The PM Type System

In common with other aspects of PM design, the type system is designed to facilitate the use of flexible programming constructs, such as polymorphism and dynamic dispatch, while emphasising the generation of fast, static, code.  In common with many other modern languages, PM also attempts to combine the safety of static typing with the expressivity of dynamically typed languages such as Python . The type of any PM expression may usually be determined by a static examination of the code. Variables take their types from an initialising expression using the ‘:=’ declaration syntax popularised by Go . a:= 100 ! Declare ‘a’ as an integer (int) variable Variables do not change type during the course of their lifetime – polymorphic programming requires the use of special values, described later. Composite values such as structures are generated using specialised expressions rather than through the invocation of type-specific creator functions: b:= struct var_descriptor...