I will say though that I'm reluctant to program any kind of automatic
optimisation that is not thread-safe, even with the 'volatile'
intrinsic. A 'safe' directive is a way of telling the compiler that
"this routine will not be affected by multi-threaded shenanigans" so is
safe to make thread-unsafe optimisations, so long as aliasing and the
like doesn't catch me off-guard.
At the same time, if you're feeling very ballsy (or you know exactly
what you're doing), you could mark a routine as safe so most global
values benefit from potential optimisations, but the one value that is
not thread-safe you can put inside a volatile intrinsic. At least
that's the plan. The idea is that directives and modifiers only remove
compiler safety, not add to it because of an uncertain optimisation.
Gareth
On 04/05/2019 17:22, J. Gareth Moreton wrote:
Ah, slightly misinterpreted the aliasing thing... yeah, that is a
danger to consider. I'll have to think about that one. That alone
may make safe procedures, at least ones with var and out parameters,
relatively impossible.
Gareth aka. Kit
On 04/05/2019 17:06, J. Gareth Moreton wrote:
This is why I posted to the group! You can catch things that I might
miss.
For the aliasing issue, I envisioned that var parameters wouldn't be
affected. Since the address is already in a register or on the
stack, it's relatively efficient already.
For the subroutine call, that is indeed a little more difficult, and
the compiler would have to consider that a subroutine call may modify
one of the non-local values.
C/C++ was never my first language, so generally I try not to mirror
it. I even joked that seeing C-style standards in Pascal source is
tantamount to colonialism!
I've noticed that my take on coding is somewhat different to others
at times. I tend not to trust the compiler to make the most
efficient code. I think the best analogy would be the difference
between the Quake and the Unreal engines... Quake does all the BSP
and portal building itself during a map compilation stage, while with
the Unreal engine, the mapper decides where portals and the like go.
It is a tiny bit more work, but it allowed for highly detailed and
optimised maps so long as you used a bit of logical thinking.
The reason why I suggested a modifier directive is for similar
reasons why I'm doing the same thing with pure functions... for
compilation speed. If a function is marked as pure, the compiler
will have to do a lot more processing and analysis, so if all
functions are implicitly considered pure until proven otherwise, it
will slow down compilation significantly. A similar thing may happen
with safe functions because it will have to undertake data flow
analysis. It's hard to say if the compiler performance hit will be
significant or not, but you may be right in that safe procedures can
be merged with data-flow analysis if it becomes a major part of the
compiler. The only risk is with multi-threading again - if a
procedure suddenly behaves differently under the highest optimisation
settings because of the lack of a 'volatile' intrinsic, I personally
consider it a bug (which is why I'm not a fan of -O4 with its
advertised 'may cause side-effects').
It does make for some interesting discussion though!
Gareth aka. Kit
On 04/05/2019 09:37, Jonas Maebe wrote:
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
---
This email has been checked for viruses by Avast antivirus software.
https://www.avast.com/antivirus
_______________________________________________
fpc-devel maillist - fpc-devel@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
_______________________________________________
fpc-devel maillist - fpc-devel@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
_______________________________________________
fpc-devel maillist - fpc-devel@lists.freepascal.org
http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel