[comp.lang.ada] Pragma SHARED, tasks and shared variables

IVANOVIC%VAXR@CIRCUS.LLNL.GOV ("Vladimir Ivanovic, x3-7786") (07/28/88)

    Can anyone answer the following question:
    
    If I specify that every read or update to an variable whose type is an
    access type is a synchronization point for that variable by using the
    pragma SHARED and naming that variable [LRM 9.11(7)], can I be assured
    that the object designated by that variable will never be a local
    copy of that object?
    
    My intent is to get around the restrictions imposed by LRM 9.11(3)
    and 9.11(8) by having shared data structures whose types are not
    restricted to scalar and access types.
    
    My motivation is performance.  I cannot afford the CPU strokes that
    encapsulating the data structures in a task and performing rendezvous
    with that task to access the data would cost.  (And I cannot afford a
    faster CPU...)
    
    I realize that my particular implementation (VAX Ada v1.4) allows
    the use of pragma VOLATILE which is the same as pragma SHARED without
    the words "and whose type is a scalar or access type", essentially
    giving me what I want, but pragma VOLATILE is not portable across
    implementations.  

NCOHEN@IBM.COM (Norman Cohen) (07/28/88)

Ref: INFO-ADA Volume 88 Issue 177 (Thu, Jul 28, 1988) Item #4

Vladimir Ivanovic asks:

>> If I specify that every read or update to an variable whose type is an
>> access type is a synchronization point for that variable by using the
>> pragma SHARED and naming that variable [LRM 9.11(7)], can I be assured
>> that the object designated by that variable will never be a local
>> copy of that object?

First let me rephrase the question, since the notion of a single access
value designating one variable (a shared variable) at one time and
another variable (a local copy) at another time doesn't make sense.  The
real question is whether, between synchronization points, a local copy
can be used IN PLACE OF the variable designated by an access value.

The first sentence of 9.11(2) informally defines a shared variable as a
variable "accessible" by more than one task.  Thus, if more than one task
executes some task body in the scope of an access type, all variables in
the collection associated with the access type are shared variables.

Therefore, an optimizing compiler may assume, when compiling one task
that uses a designated variable, that no other task updates the same
designated variable between synchronization points.  Consider, for
example, the following code:

   TYPE Reading_Type IS DELTA ... RANGE ...;
   TYPE Reading_Pointer_Type IS ACCESS Reading_Type;

   Reading_Pointer    : Reading_Pointer_Type;
   Shutdown_Requested : Boolean;
   Polling_Interval   : CONSTANT Duration := ...;
   Next_Polling_Time  : Calendar.Time :=
                           Calendar.Clock + Polling_Interval;

   PRAGMA Shared(Reading_Pointer);
   PRAGMA Shared(Shutdown_Requested);

   ...

   WHILE NOT Shutdown_Requested LOOP
      Poll_Device(Reading_Pointer.ALL);
      DELAY Next_Polling_Time - Calendar.Clock;
      Next_Polling_Time := Next_Polling_Time + Polling_Interval;
   END LOOP;

Evaluation of the name Reading_Pointer.ALL examines the variable
Reading_Pointer, so the Shared pragma makes that name a synchronization
point for the variable Reading_Pointer, but NOT for the variable
Reading_Pointer.ALL.  The notion of a synchronization point for a
particular variable (as opposed to for a task) is introduced in 9.11(9):

     The pragma SHARED can be used to specify that every read or
     update of a variable is a synchronization point for that
     variable; that is, the above assumptions always hold for the
     given variable (but not necessarily for other variables).

Therefore, the loop above could be optimized, in accordance with 9.11, to
the following:

   <register X> := Reading_Pointer.ALL;
   WHILE NOT Shutdown_Requested LOOP
      Poll_Device( <register X> );
      DELAY Next_Polling_Time - Calendar.Clock;
      Next_Polling_Time := Next_Polling_Time + Polling_Interval;
   END LOOP;
   Reading_Pointer.ALL := <register X>;

Of course this optimization is illegitimate, even from a single-task
point of view, if some other access-type variable contains the same
access value as Reading_Pointer, and other statements refer to the
variable designated by that other access-type variable.  Thus, as a
practical matter, the optimization can PROBABLY be defeated as follows:

   PROCEDURE Make_Value_Unknown(Pointer: OUT Reading_Pointer_Type)
      IS SEPARATE;

   Dummy_Pointer : Reading_Pointer_Type; -- type of Reading_Pointer

   ...

   Make_Value_Unknown(Dummy_Pointer);
   WHILE NOT Shutdown_Requested LOOP
      Poll_Device(Reading_Pointer.ALL);
      Dummy_Pointer.ALL := Dummy_Pointer.ALL + Dummy_Pointer.ALL;
      DELAY Next_Polling_Time - Calendar.Clock;
      Next_Polling_Time := Next_Polling_Time + Polling_Interval;
   END LOOP;

   ...

   SEPARATE(...)
   PROCEDURE Make_Value_Unknown(Pointer: OUT Reading_Pointer_Type) IS
   BEGIN
      Pointer := NEW Reading_Type'(0.0);
         -- Repeated addition of zero won't overflow
   END Make_Value_Unknown;

Unless the optimizing compiler performs cross-compilation-unit
optimizations at link time, or unless it actually compares pointer values
before the loop, it will not be able to determine whether
Dummy_Pointer = Reading_Pointer inside the loop.  Thus, aside from any
considerations about what other tasks are doing, the compiler will be
unable to make a local copy of Reading_Pointer.ALL.

Of course this solution is less than satisfying.  It is tricky and
obscure, it depends on certain assumptions about the behavior of the
optimizing compiler, and it requires useless code to be added to the loop
to reference Dummy_Pointer.ALL.  I would much have preferred language
rules that permitted optimizations with surprising consequences (both the
kind permitted by LRM 9.11 and the kind permitted by LRM 11.6) only in
the presence of a pragma explicitly requesting such optimizations.

Norman Cohen
IBM Research

jonab@CAM.UNISYS.COM (Jonathan P. Biggar) (07/28/88)

In article <8807280055.AA27279@ajpo.sei.cmu.edu> IVANOVIC%VAXR@CIRCUS.LLNL.GOV ("Vladimir Ivanovic, x3-7786") writes:
>    I realize that my particular implementation (VAX Ada v1.4) allows
>    the use of pragma VOLATILE which is the same as pragma SHARED without
>    the words "and whose type is a scalar or access type", essentially
>    giving me what I want, but pragma VOLATILE is not portable across
>    implementations.  

However, pragma SHARED is not portable across implementations either.  You
cannot predict what any given implementation is going to consider a "scalar"
type that can be accessed atomically.  Implementations that support SHARED
probably will not allow you to use it on a variable that is not scalar or
access anyway.

Try and look at your system again and see if you can change the way you
allocate tasks in order to reduce the severity of your problem.  For example,
if you need to access the data structure several times in a short period,
consider using a task that just acts as a semaphore, allowing another
task to seize the resource, access the data structure directly, and then
release the resource.

Jon Biggar
jonab@cam.unisys.com