[fpc-devel] Possible idea... "safe" subroutines/methods
J. Gareth Moreton
gareth at moreton-family.com
Sat May 4 18:56:45 CEST 2019
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 at 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 at lists.freepascal.org
>> http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
>>
>>
> _______________________________________________
> fpc-devel maillist - fpc-devel at lists.freepascal.org
> http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
>
>
More information about the fpc-devel
mailing list