On 2/2/21 11:06 PM, J. Gareth Moreton via fpc-devel wrote:
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="&lt;no type symbol&gt;" 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="&lt;no type symbol&gt;" 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.

You can introduce a new pass in the compiler, that does the node transformation only on platforms that benefit from it. See e.g. what I did a few years ago in optloadmodifystore.pas in the compiler sources. This is an optimization that introduces new nodes that generate faster code on x86 for patterns, such as:

a := a + b

Which can be done with a single 'add mem, const' or 'add mem, reg' instruction on all processors in the x86 family (i8086, i386 and x86_64). Also, I think there were other CPUs that benefit from this optimization (m68k?). For that, I introduced new inline compiler nodes, that are only used for this optimization.


(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)

Introducing norn and nand for optimization purposes might not be a bad idea, considering there are some CPU architectures that also have nor and nand instructions (powerpc, for example).

Best regards,

Nikolay

P.S. I don't think we need "WARNING: Technical!" trigger warnings on fpc-devel ;-)

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

Reply via email to