Sign in Join

Live chat for Free Pascal, Lazarus, Delphi and every Pascal programmer.

Mailing lists / fpc-devel / 2019-07

Deeper problem with Internal Error 200309201

12 messages · 3 people · –

Reply by email Subscribe to this list Your subscription

Read-only archive. Use j and k to move between messages.

J. Gareth Moreton

Hi everyone,

So my patch to fix #32913
<https://bugs.freepascal.org/view.php?id=32913> was unfortunately rejected because it relied on a slightly hacky feature in the form of a new state variable that, currently, is only used to patch this particular issue, and as Florian has hinted in the issue notes, a deeper fix needs to be investigated.

What I can put together so far is that if you have a construct like this...

try
except
   try
     [b]
   finally
     [c]
   end;
end;

...and [b] is not empty ([c] doesn't matter), then the compiler will trigger Internal Error 200309201.  What happened is that during the simplify stage of ttryexceptnode, if the try block is empty (represented by the 'left' node), the compiler would replace the entire construct with a 'nothing' node.  Logically a sound choice, because it's impossible for an exception to be raised in this instance.  The problem comes that the except section contains a try..finally block which sets up a stack unwind block.  It's a little hard to follow at this point, but what it culminates in is that there's a non-null exception filter (try..finally and try..except get bundled together for the stack unwind) that the compiler is not expecting later on, which causes a sanity check to fail and trigger the internal error.

One approach is to simply not transform the except section into a nothing node, but this adds significant overhead to the compiled binaries and incur a performance penalty by having an unnecessary stack unwind (such try..except blocks may occur in the case of inlined functions yielding no code, for example).

My first attempt at fixing this was to move the 'simplify' code into 'pass_1' for ttryexceptnode, and if it detected an empty try section, to simply not run "pass_1" on anything in the except section, so no code is ever put into the exception filter - I argued this logic to myself because, as already explained, this exception handler would never be executed.  This worked, but caused a regression in tests/tbf/tb0089.pp and tests/tbf/tb0090.pp - these tests had an empty try section in their try..except blocks, because all they had to test was to attempt to use 'goto' from the except section to jump to a label outside of the block.  This particular syntactic check was only performed in "pass_1", so by not running it for those nodes, the syntax check was skipped and the tests erroneously compiled successfully.

Since this approach resulted in a regression, it wasn't acceptable and so I had to find some other means to prevent code entering the exception filter without completely overhauling the node parser.  This led to what is how the "current_nodes_dead" flag, set if the try section is empty and designed with future expansion in mind (if you know nodes are unreachable, you can skip doing any kind of code generation or even delete them entirely once you do the syntactic checking).   After writing the necessary code in the ttryfinallynode class, I got the issue fixed at long last.

It goes without saying that I was upset that the patch was rejected, especially as this particular issue has been on my books for literally a year now, and I had no other viable solution to offer since everything else I had tried resulted in an error elsewhere.  However, now that Florian's actually responded and hinted at a willingness to look for a bigger solution, I'm able to look at it more objectively.  Having a flag that's only used to fix one feature is a little dangerous, even with a proposal for future use, because chances are it will be forgotten about and people will just introduce other flags when and where they need.

I would like some discussion on this with the administrative team because this does require some careful design if we're going to do this properly.  Building on Florian's suggestion, I would like to propose splitting "pass_1" into "pass_syntax" and a pass that transforms the nodes (I would name it "pass_transform", but this doesn't sound 'important' enough, given it is effectively compiling the nodes).  "pass_syntax" would be a public method declared in tnode as the following "function pass_syntax: Boolean; virtual;" - if it returns True, then everything was fine; if not, it returns False and this can be used to set the error flag.  By being defined this way, this is no ability for the node to transform itself, but it can set private fields based on the syntax it finds (by setting private fields, the "first pass" won't have to repeat some of the work needed to determine how to transform the nodes).

There are two ways this can then be implemented... one way is to have the syntax pass be completely separate from the now badly-named "first pass" and just do syntax checking on all the nodes.  If any syntax errors are found, then the error flag can be set and "firstpass" will skip these nodes already.  After this syntax pass is done, then the compiler can move onto the "first pass".  The drawback to this is that it will likely increase compilation time quite noticably, since the compiler will be running an additional traversal through the entire node tree.

The second way is to have the syntax pass as the first step of "firstpass", so an extra traversal is not required.  However, this will be harder to develop and maintain because care has to be taken that all nodes are syntax-checked but not transformed if they would result in dead or unexpected code (as with internal error 200309201).  Given this would require a flag for a 'dead node', we may end up in a similar situation as with the "hacky" patch.  However, one thought that occurred to me is that we can make a new flag for tnodeflag named something like 'nf_dead', since this doesn't add any new fields, and if this flag is set, then "pass_1" and "simplify" are not called, and it cascades the flag to the child nodes.  This would be the 32nd flag for tnodeflag, so it reaches the upper limit for a small set - something to keep in mind if it gets expanded later.

On the surface, the first implementation suggestion feels like the cleaner option, but I don't know how much of a penalty it will add to the compiler.  Personally I would like to attempt a design for the second implementation to keep the number of passes down to a minimum and the compiler reasonably fast (also my motivation behind the x86_64 peephole optimizer overhaul). But that's just my opinion.

On an additional note, I do wonder if it's possible to merge "pass_1" and "simplify", since they are treated pretty much identically in the "firstpass" routine - if either of them return something other than nil, it is considered to be a node transformation, running this block of code (there are two copies, one for each call):

p.free;
p := hp;
firstpass(p);

(hp contains the return value of whatever method was just called) This may be impractical because "simplify" is called elsewhere for some nodes.

At least that's what I've thought about so far.  Where do we go from here?  I sense for this task, I should write up a PDF design spec like I did with the optimizer overhaul, but this is something that needs a fair bit of discussion.  And implementation will be a fairly mammoth task because it would require introducing the new method to every single node type.

Thank you for your time, and I hope we can find the best solution to this issue.

Gareth aka. Kit

---
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Sven Barth
to J. Gareth Moreton
J. Gareth Moreton wrote 14 lines
I would like some discussion on this with the administrative team because this does require some careful design if we're going to do this properly.  Building on Florian's suggestion, I would like to propose splitting "pass_1" into "pass_syntax" and a pass that transforms the nodes (I would name it "pass_transform", but this doesn't sound 'important' enough, given it is effectively compiling the nodes).  "pass_syntax" would be a public method declared in tnode as the following "function pass_syntax: Boolean; virtual;" - if it returns True, then everything was fine; if not, it returns False and this can be used to set the error flag.  By being defined this way, this is no ability for the node to transform itself, but it can set private fields based on the syntax it finds (by setting private fields, the "first pass" won't have to repeat some of the work needed to determine how to transform the nodes).

"pass_syntax" is not a good name, because it does *not* do syntax checking (that was done by the parser). Instead it checks the semantic, so "pass_semantic" would be a more correct name.

Also I don't think a Boolean result is needed. Take a look at tgotonode.pass_1 which is after all the one which would need to be moved to pass_semantic: it uses CGMessage*() to write its messages (as all nodes do) which in turn sets the codegenerror flag if it wasn't set already. So after a call to a node's pass_semantic method all that needs to be checked is codegenerror to decide how to proceed further. Thus the semantic pass could simply be "procedure pass_semantic;virtual;".

Show quoted text 6 lines
On the surface, the first implementation suggestion feels like the cleaner option, but I don't know how much of a penalty it will add to the compiler. Personally I would like to attempt a design for the second implementation to keep the number of passes down to a minimum and the compiler reasonably fast (also my motivation behind the x86_64 peephole optimizer overhaul).  But that's just my opinion.

In my opinion a clean, easily maintainable solution is preferrable.

Show quoted text 13 lines
On an additional note, I do wonder if it's possible to merge "pass_1" and "simplify", since they are treated pretty much identically in the "firstpass" routine - if either of them return something other than nil, it is considered to be a node transformation, running this block of code (there are two copies, one for each call): p.free; p := hp; firstpass(p); (hp contains the return value of whatever method was just called) This may be impractical because "simplify" is called elsewhere for some nodes.

I don't think combining the two passes is necessary. The ideas behind the two passes are after all different.

Regards,
Sven

Reply by email

J. Gareth Moreton
to Sven Barth

Points seem fair so far.  And true, the Boolean result won't be necessary because the error flag will be set, and this can be checked instead.

While the idea behind "pass_1" and "simplify" are different, to me it isn't always clear what is simplification and what is regular code pass, and is often just left to the discretion of the programmer.

Also, though not a major point, with the code that handles a transformed node...

p.free;
p := hp;
firstpass(p);

...there could be a huge performance gain if, instead of calling firstpass(p), it just jumps to the top of the procedure (although after the code that preserves the state).  The code that follows is too complicated for the compiler to see a tail recursion, but if you follow the code logic from such a call, p has already had pass 1 completed by the time it exits, regardless of how much it gets transformed.  That's that something that would test on the side though, but might be nice if it helps offset the performance penalty tha comes from adding another tree traversal.

The other saving is that a lot of the semantic checking (seems I need to learn the difference between 'syntactic' and 'semantic') will be moved out of "pass_1" and the routine will just transform and traverse the nodes based on what it learnt from the "pass_semantic" stage.

Note that it won't just be the tgotonode that requires rewriting to take advantage of the semantic stage, but virtually every node so that all semantics are checked, but code is not generated if it's not necessary, since it was that that caused the internal error that started this mess in the first place!

What do others think?

Gareth aka. Kit

P.S. If I were to take on the challenge of adapting this node pass thing, would you like a design specification to go with it so it is easier to explain and justify what's going on and so future developers can get to grips with the compiler's internals?

P.P.S. The nice thing about method and field names is that, if I get them wrong, they are very easy to rename in Lazarus.

Sven Barth wrote 63 lines
Am 13.07.2019 um 14:12 schrieb J. Gareth Moreton: > I would like some discussion on this with the administrative team > because this does require some careful design if we're going to do > this properly.  Building on Florian's suggestion, I would like to > propose splitting "pass_1" into "pass_syntax" and a pass that > transforms the nodes (I would name it "pass_transform", but this > doesn't sound 'important' enough, given it is effectively compiling > the nodes). "pass_syntax" would be a public method declared in tnode > as the following "function pass_syntax: Boolean; virtual;" - if it > returns True, then everything was fine; if not, it returns False and > this can be used to set the error flag.  By being defined this way, > this is no ability for the node to transform itself, but it can set > private fields based on the syntax it finds (by setting private > fields, the "first pass" won't have to repeat some of the work needed > to determine how to transform the nodes). "pass_syntax" is not a good name, because it does *not* do syntax checking (that was done by the parser). Instead it checks the semantic, so "pass_semantic" would be a more correct name. Also I don't think a Boolean result is needed. Take a look at tgotonode.pass_1 which is after all the one which would need to be moved to pass_semantic: it uses CGMessage*() to write its messages (as all nodes do) which in turn sets the codegenerror flag if it wasn't set already. So after a call to a node's pass_semantic method all that needs to be checked is codegenerror to decide how to proceed further. Thus the semantic pass could simply be "procedure pass_semantic;virtual;". > On the surface, the first implementation suggestion feels like the > cleaner option, but I don't know how much of a penalty it will add to > the compiler. Personally I would like to attempt a design for the > second implementation to keep the number of passes down to a minimum > and the compiler reasonably fast (also my motivation behind the > x86_64 peephole optimizer overhaul).  But that's just my opinion. In my opinion a clean, easily maintainable solution is preferrable. > On an additional note, I do wonder if it's possible to merge "pass_1" > and "simplify", since they are treated pretty much identically in the > "firstpass" routine - if either of them return something other than > nil, it is considered to be a node transformation, running this block > of code (there are two copies, one for each call): > > p.free; > p := hp; > firstpass(p); > > (hp contains the return value of whatever method was just called) > This may be impractical because "simplify" is called elsewhere for > some nodes. > I don't think combining the two passes is necessary. The ideas behind the two passes are after all different. Regards, Sven _______________________________________________ fpc-devel maillist  -  https://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

Reply by email

J. Gareth Moreton
to J. Gareth Moreton: Points seem fair so far.  And true, the Boolean result won't be necessary because the…

So having a long think over the past day, I'm starting to turn against the second idea I had because it would require complex state variable tracking and is just asking for a new bug to be introduced, not to mention that the additional overhead will probably offset any potential speed gains, so the cleaner, simple solution sounds far more appealing now.  Also, when it comes to dead nodes (an except section that gets removed or any code following "Exit" in the same block), there's nothing to stop the compiler from deleting the nodes completely, since semantics have already been checked (and maybe chucking in an extra "unreachable code" warning if needs be), thus pruning the tree and making the process faster for pass_generate_code.

I do wonder why 'simplify' is a distinct method though, because a lot of the time one can simply put its code at the end of 'pass_1' and there wouldn't be any difference in functionality (plus what counts as 'pass_1' and what counts as 'simplify' is often left to the programmer's discretion) while nodes that have more complex simplification, like taddnode, could use their own private methods.  It might be something for me to experiment with on the side.

So the node parsing overhaul, so far...

- pass_semantic
- pass_transform (what was "pass_1")
- simplify (if not merged into the above)
- pass_generate_code (I won't touch this for now)

On another note, I do think pass_2 (pass_generate_code) could use some refactoring.  I don't like how "flowcontrol" is a global variable.  Though it's unlikely to happen, such a state variable not being tied to a management object (e.g. current_procinfo) prevents the compiler from being multi-threaded, at least for that pass.

Gareth aka. Kit

P.S. Anyone got a better name for "pass_transform"?

---
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Sven Barth
to J. Gareth Moreton
J. Gareth Moreton wrote 5 lines
On another note, I do think pass_2 (pass_generate_code) could use some refactoring.  I don't like how "flowcontrol" is a global variable.  Though it's unlikely to happen, such a state variable not being tied to a management object (e.g. current_procinfo) prevents the compiler from being multi-threaded, at least for that pass.

There are many other global variables that prevent that as well... That said I welcome work that changes that (though of course it needs to handle exception/finally handlers that are outside of the containing procedure (like is the case for x86_64-win64) as well).

Regards,
Sven

Reply by email

J. Gareth Moreton
to J. Gareth Moreton: So having a long think over the past day, I'm starting to turn against the second idea I…

So what's the verdict so far? Do I have your blessing to start work on this?

Because of its complexity, I'll do one of my PDF design specs as well.

Admittedly my mind is drifting a little bit because I want to see how much optimisation can be done at the node level (e.g. converting "x * 1" to just "x") so as to take some strain off the peephole optimizer, but got to do the framework first.

Gareth aka. Kit

P.S. Still waiting for further feedback on the x86_64 optimizer overhaul.  Granted it's a hard one to evaluate because of its complexity.

---
This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus

Reply by email

Florian Klämpfl
to J. Gareth Moreton: So having a long think over the past day, I'm starting to turn against the second idea I…
J. Gareth Moreton wrote 12 lines
So having a long think over the past day, I'm starting to turn against the second idea I had because it would require complex state variable tracking and is just asking for a new bug to be introduced, not to mention that the additional overhead will probably offset any potential speed gains, so the cleaner, simple solution sounds far more appealing now.  Also, when it comes to dead nodes (an except section that gets removed or any code following "Exit" in the same block), there's nothing to stop the compiler from deleting the nodes completely, since semantics have already been checked (and maybe chucking in an extra "unreachable code" warning if needs be), thus pruning the tree and making the process faster for pass_generate_code. I do wonder why 'simplify' is a distinct method though, because a lot of the time one can simply put its code at the end of 'pass_1' and there wouldn't be any difference in functionality (plus what counts as 'pass_1' and what counts as 'simplify' is often left to the programmer's discretion) while nodes that have more complex simplification, like taddnode, could use their own private methods.  It might be something for me to experiment with on the side.

The idea is to keep passes distinct. It makes maintainace easier. Besides this, Simplify is made to be run multiply times, pass_1 not.

So the node parsing overhaul, so far... - pass_semantic

Yes.

- pass_transform (what was "pass_1")

I wouldn't change this in one patch. Step by step ...

Show quoted text 10 lines
- simplify (if not merged into the above) - pass_generate_code (I won't touch this for now) On another note, I do think pass_2 (pass_generate_code) could use some refactoring.  I don't like how "flowcontrol" is a global variable.  Though it's unlikely to happen, such a state variable not being tied to a management object (e.g. current_procinfo) prevents the compiler from being multi-threaded, at least for that pass. Gareth aka. Kit P.S. Anyone got a better name for "pass_transform"?

pass_1 was for years, I wouldn't change it for the time being.

Reply by email

J. Gareth Moreton
to Florian Klämpfl

Looks like we have a deal then!  Break away code from "pass_1" into "pass_semantic".

Thanks for your support Florian - I'll start making plans.

Gareth aka. Kit

Florian Klämpfl wrote 40 lines
Am 14.07.2019 um 04:25 schrieb J. Gareth Moreton: > So having a long think over the past day, I'm starting to turn against the second idea I had because it would require > complex state variable tracking and is just asking for a new bug to be introduced, not to mention that the additional > overhead will probably offset any potential speed gains, so the cleaner, simple solution sounds far more appealing now. > Also, when it comes to dead nodes (an except section that gets removed or any code following "Exit" in the same block), > there's nothing to stop the compiler from deleting the nodes completely, since semantics have already been checked (and > maybe chucking in an extra "unreachable code" warning if needs be), thus pruning the tree and making the process faster > for pass_generate_code. > > I do wonder why 'simplify' is a distinct method though, because a lot of the time one can simply put its code at the end > of 'pass_1' and there wouldn't be any difference in functionality (plus what counts as 'pass_1' and what counts as > 'simplify' is often left to the programmer's discretion) while nodes that have more complex simplification, like > taddnode, could use their own private methods.  It might be something for me to experiment with on the side. The idea is to keep passes distinct. It makes maintainace easier. Besides this, Simplify is made to be run multiply times, pass_1 not. > So the node parsing overhaul, so far... > > - pass_semantic Yes. > - pass_transform (what was "pass_1") I wouldn't change this in one patch. Step by step ... > - simplify (if not merged into the above) > - pass_generate_code (I won't touch this for now) > > On another note, I do think pass_2 (pass_generate_code) could use some refactoring.  I don't like how "flowcontrol" is a > global variable.  Though it's unlikely to happen, such a state variable not being tied to a management object (e.g. > current_procinfo) prevents the compiler from being multi-threaded, at least for that pass. > > Gareth aka. Kit > > P.S. Anyone got a better name for "pass_transform"? pass_1 was for years, I wouldn't change it for the time being. _______________________________________________ fpc-devel maillist - https://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

Reply by email

J. Gareth Moreton
to J. Gareth Moreton

So a progress update.

I ended up running into some difficulty with the original idea of having a full semantic pass that is run prior to the first pass. For some reason, "pass_semantic" wasn't always executed for every node, and I still haven't figured out why (although it seems to be related to call parameter nodes), so instead I had to have it call "pass_semantic" as the first pass, which goes through the tree that branches from that particular node and calls "pass_semantic" recursively for each node it finds and flags it when complete.

As a result it's become a bit of a hybrid of the first and second ideas I had, although it still feels fairly clean in that changes have been minimal and easy to follow.  However, a new "semantics done" flag was required, largely for performance reasons (it reduces the complexity of the semantics pass from O(n²) to about O(n), otherwise a large portion of the tree would be re-evaluated each time firstpass is called, since the semantics pass MUST evaluate the children before they have a chance to get pruned or transformed by "pass_1").  To facilitate this, I introduced a new field in the TNode ancestor class named "compileflags", which contains flags that are only useful during the compilation process and are not stored in a PPU file because their values are deterministic upon a successful compilation.  These compile flags are "cf_error" (was originally the "nf_error" flag - should always be 0 in a PPU file), "cf_pass1_done" (was originally the "nf_pass1_done" flag - should always be 1 in a PPU file) and the new "cf_semantics_done" flag.  The two gaps in the original tnodeflag enumeration now contain "nf_reserved1" and "nf_reserved2" so it doesn't change the bit ordering in existing PPU files.  Given the enumeration already had 31 values out of a safe maximum of 32, this has freed up two extra bits for future expansion without having to change the PPU format in any way.

Ideally I want to figure out why "pass_semantic" isn't getting called in some situations, as that would remove the need to check the flag at the start of every call to "firstpass".  Besides, the intention and ambition is to have all the semantic checks moved to "pass_semantic" (currently only the ones originally in 'tgotonode.pass_1' were somewhat critical), so "pass_1" is free to prune child nodes if it so desires.  This is how "ttryexceptnode.pass_1" now resolves issue 32913... if the try block is completely empty, it frees and removes the "right" and "t1" nodes, which equate to the "on" and "else" parts of the except section, so they cannot be used to generate code that the compiler is not expecting, but also without overlooking semantic checks like an illegal jump to a label outside of the except block.  I figure that other node types may get refactored later to take advantage of this.

Nevertheless, my code currently compiles successfully and fixes issue 32913 without causing tbf/tb0089.pp or tbf/tb0090.pp to fail.  I'll be writing up a PDF file before submitting the patches though because I feel this is something that needs review before being accepted.  I apologise if this is a lot to take in, but once you see the document and my patches, I believe they will look satisfactory and clean.

Gareth aka. Kit

J. Gareth Moreton wrote 72 lines
Looks like we have a deal then!  Break away code from "pass_1" into "pass_semantic". Thanks for your support Florian - I'll start making plans. Gareth aka. Kit On 14/07/2019 19:45, Florian Klämpfl wrote: > Am 14.07.2019 um 04:25 schrieb J. Gareth Moreton: >> So having a long think over the past day, I'm starting to turn >> against the second idea I had because it would require >> complex state variable tracking and is just asking for a new bug to >> be introduced, not to mention that the additional >> overhead will probably offset any potential speed gains, so the >> cleaner, simple solution sounds far more appealing now. >> Also, when it comes to dead nodes (an except section that gets >> removed or any code following "Exit" in the same block), >> there's nothing to stop the compiler from deleting the nodes >> completely, since semantics have already been checked (and >> maybe chucking in an extra "unreachable code" warning if needs be), >> thus pruning the tree and making the process faster >> for pass_generate_code. >> >> I do wonder why 'simplify' is a distinct method though, because a >> lot of the time one can simply put its code at the end >> of 'pass_1' and there wouldn't be any difference in functionality >> (plus what counts as 'pass_1' and what counts as >> 'simplify' is often left to the programmer's discretion) while nodes >> that have more complex simplification, like >> taddnode, could use their own private methods.  It might be >> something for me to experiment with on the side. > The idea is to keep passes distinct. It makes maintainace easier. > Besides this, Simplify is made to be run multiply > times, pass_1 not. > >> So the node parsing overhaul, so far... >> >> - pass_semantic > Yes. > >> - pass_transform (what was "pass_1") > I wouldn't change this in one patch. Step by step ... > >> - simplify (if not merged into the above) >> - pass_generate_code (I won't touch this for now) >> >> On another note, I do think pass_2 (pass_generate_code) could use >> some refactoring.  I don't like how "flowcontrol" is a >> global variable.  Though it's unlikely to happen, such a state >> variable not being tied to a management object (e.g. >> current_procinfo) prevents the compiler from being multi-threaded, >> at least for that pass. >> >> Gareth aka. Kit >> >> P.S. Anyone got a better name for "pass_transform"? > pass_1 was for years, I wouldn't change it for the time being. > _______________________________________________ > fpc-devel maillist  -  > https://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  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email

J. Gareth Moreton
to J. Gareth Moreton

Note that there is possible room for improvement with this concept (like maybe only running the semantic check on nodes that exist before the first pass, not executing it on any transformed nodes), but I wanted to get something clean working first, and fix the bugs that have been on the system for a while.

Gareth aka. Kit

J. Gareth Moreton wrote 19 lines
I've completed my work, although I had to adapt the original plan slightly, and I feel like my design spec could use some rewriting in places.  Think of it as code smell for writing! https://bugs.freepascal.org/view.php?id=35857 - the PDF design spec is here. Gareth aka. Kit --- This email has been checked for viruses by Avast antivirus software. https://www.avast.com/antivirus _______________________________________________ fpc-devel maillist  -  https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply by email