On 2019-05-03 19:37, J. Gareth Moreton wrote:
By telling the compiler that the procedure (or maybe a whole class) is
thread-safe, you are telling it that you can guarantee that any
objects, fields or global variables that you access are guaranteed to
not suddenly change mid-routine (because another thread has modified
it).  This would allow the compiler to move commonly-accessed fields
into local registers or the stack for faster access, especially if the
fields are only read and not written, since they'll be guaranteed to
contain a constant value.

Multi-threading is not the main issue. The main problems are aliasing and subroutine calls:

1) Aliasing

type
  tc = class
    a: longint;
    procedure test(var l: longint);
  end;

procedure tc.test(var l: longint);
begin
  if a<>5 then
    begin
      l:=1;
// the above will change c.a to 1, but if c.a is in a register that will not be detected
      if a<>1 then
        writeln('error');
    end;
end;

var
  c: tc;
begin
  c:=tc.create;
  c.a:=6;
  c.test(c.a);
  c.free;
end.


2) subroutine calls

type
  tc = class
    a: longint;
    procedure test;
  end;

var
  c: tc;

procedure change;
begin
  c.a:=1;
end;

procedure tc.test;
begin
  if a<>5 then
    begin
      change;
      if a<>1 then
        writeln('error');
    end;
end;

begin
  c:=tc.create;
  c.a:=6;
  c.test;
  c.free;
end.

In both cases, many additional scenarios are possible (there are many different way to alias memory and to perform modifications in subroutine calls).

For the former, you need inter-procedural alias analysis, or limit yourself to routines that only write to local variables. For the latter, you need to limit yourself to routines that don't call other routines, and/or record various function attributes that indicate what these other routines do. See e.g. the function attributes from LLVM (http://llvm.org/docs/LangRef.html#function-attributes) like inaccessiblememonly, inaccessiblemem_or_argmemonly, readnone, readonly, writeonly, and argmemonly. Since LLVM found a use for them in terms of optimising code, they're probably a good a start.

I wish to stress that I do _not_ propose or support adding any of those attributes to the language; most of those attributes don't exist in C/C++ either. They get added by LLVM itself while optmising and analysing the functions, or by compiler backends for auto-generted functions.

However, you could add compiler analyses that add those, or similar, attributes to the implementation procdef flags (tprocdef.implprocoptions), and then make use of those attributes even in cross-unit calls (in case the body of the function in the other unit has already been compiled, similar to inlining). Or in case of whole-program optimisation, they could written and loaded for the entire program, so you can use them even when function bodies have not yet been parsed.

As far as the threading issue is concerned: trunk has support for the "volatile" intrinsic. At most, I would add an optimizer option that prevents optimisations that may break things in case "volatile" is missing. This should happen in very few places though, since it can only change the behaviour of a well-defined program if you are busy-waiting on a single value that another thread may change (and do nothing else with values produced by this other thread, unless you also add a bunch of memory barriers and, depending on the architecture, also acquire/release helpers).


Jonas
_______________________________________________
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply via email to