[fpc-devel] Discussion on a particular optimisation development (WARNING: Technical!)

J. Gareth Moreton gareth at moreton-family.com
Tue Feb 2 22:06:11 CET 2021


Hi everyone,

I've found a potential optimisation for conditions of the form "(x <> 0) 
and (y <> 0)", which are very common because this is semantically 
equivalent to "Assigned(x) and Assigned(y)", for example, and such a 
construct is generated implicity in TObject.Destroy, for example.  The 
node tree for TObject.Destroy example (after the first pass) is as 
follows (it checks the value of Self and VMT):

The node tree inside the if-node's condition branch is as follows:

<andn resultdef="Boolean" pos="329,9" flags="nf_pass1_done" complexity="3">
    <unequaln resultdef="Boolean" pos="329,9" flags="nf_pass1_done" 
complexity="1">
       <loadn resultdef="TObject" pos="329,9" flags="nf_pass1_done" 
complexity="0">
          <symbol>self</symbol>
       </loadn>
       <niln resultdef="TObject" pos="329,9" flags="nf_pass1_done" 
complexity="0">
       </niln>
    </unequaln>
    <unequaln resultdef="Boolean" pos="329,9" flags="nf_pass1_done" 
complexity="1">
       <typeconvn resultdef="$char_pointer" pos="329,9" 
flags="nf_pass1_done,nf_explicit,nf_internal" complexity="0" 
convtype="tc_equal">
          <typeconvn resultdef="Pointer" pos="329,9" 
flags="nf_pass1_done" complexity="0" convtype="tc_equal">
             <loadn resultdef="<no type symbol>" pos="329,9" 
flags="nf_pass1_done" complexity="0">
                <symbol>vmt</symbol>
             </loadn>
          </typeconvn>
       </typeconvn>
       <pointerconstn resultdef="$char_pointer" pos="329,9" 
flags="nf_pass1_done,nf_explicit" complexity="0">
          <value>$0000000000000000</value>
       </pointerconstn>
    </unequaln>
</andn>

On x86-64_win64, the following assembly language is generated (smart 
pipelining and out-of-order execution MIGHT permit it to be executed in 
3 cycles, but it's more likely going to take 4 or 5):

     testq    %rbx,%rbx
     setneb   %al
     testq    %rsi,%rsi
     setneb   %dl
     andb     %dl,%al

DeMorgan's Laws for 2 inputs state that "not (A or B) = not A and not 
B", and "not (A and B) = not A or not B", and using these, we can 
develop much more efficient assembly language (which will almost 
certainly take 3 cycles to run, and is much smaller):

     movq     %rbx,%rdx
     orq      %rsi,%rdx
     seteb    %al

For this particular routine, %rsi and %dl/%rdx are not used afterwards, 
and can be simplified further (this bit will probably be a peephole 
optimisation), dropping the cycle count to 2:

     orq      %rbx,%rsi
     seteb    %al

In this situation it is safe because the comparison values are already 
in registers and code generation has permitted the bypassing of Boolean 
short-circuiting.

Rather than write an entire peephole optimisation, I would ideally like 
to program this optimisation at the nodal level, and permit the compiler 
to convert it into something resembing the following ("andn" becomes 
"notn" -> "orn", and the two "unequaln" nodes become "equaln"):

<notn resultdef="Boolean" pos="329,9" flags="nf_pass1_done" complexity="3">
    <orn resultdef="Boolean" pos="329,9" flags="nf_pass1_done" 
complexity="3">
       <equaln resultdef="Boolean" pos="329,9" flags="nf_pass1_done" 
complexity="1">
          <loadn resultdef="TObject" pos="329,9" flags="nf_pass1_done" 
complexity="0">
             <symbol>self</symbol>
          </loadn>
          <niln resultdef="TObject" pos="329,9" flags="nf_pass1_done" 
complexity="0">
          </niln>
       </equaln>
       <equaln resultdef="Boolean" pos="329,9" flags="nf_pass1_done" 
complexity="1">
          <typeconvn resultdef="$char_pointer" pos="329,9" 
flags="nf_pass1_done,nf_explicit,nf_internal" complexity="0" 
convtype="tc_equal">
             <typeconvn resultdef="Pointer" pos="329,9" 
flags="nf_pass1_done" complexity="0" convtype="tc_equal">
                <loadn resultdef="<no type symbol>" pos="329,9" 
flags="nf_pass1_done" complexity="0">
                   <symbol>vmt</symbol>
                </loadn>
             </typeconvn>
          </typeconvn>
          <pointerconstn resultdef="$char_pointer" pos="329,9" 
flags="nf_pass1_done,nf_explicit" complexity="0">
             <value>$0000000000000000</value>
          </pointerconstn>
       </equaln>
    </orn>
</notn>

...but there is a catch.  In terms of the raw nodes, converting the 
"andn" node into a "notn" and an "orn" node results in a more complex 
node tree and hence less efficient assembly language in general, unless 
a particular "pass_generate_code" routine knows to look out for the 
set-up of a logical "or" that combines two variables' comparison against 
zero, like that shown above.

My question is... how should it be developed so that the node 
optimisation is performed on platforms that have the necessary assembly 
instructions, like x86_64 and AArch64 (once I develop them), but also 
not perform the optimisation on platforms that don't have the necessary 
instructions or checks in "pass_generate_code"?  Allowing the 
optimisation wouldn't cause bad code generation, but it would be more 
inefficient in the general case.

I hope I explained that okay.

(Other possibilities include introducing new node types "norn" and 
"nandn" (and maybe "nxorn" to complete the set), and having the "andn" 
node above be converted into "norn", so that they can be instantly 
converted into the relevant assembly instructions instead of relying on 
multiple steps of optimistion)

Gareth aka. Kit


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



More information about the fpc-devel mailing list