BOOLEAN EXPRESSIONS


TRUTH VALUES

As in many languages, the truth values are 0 = false, non-0 = true. Yet, after the execution of the evaluation code produced by ESA, it will always be: 0 = false, -1 = true. The code produced for the expression "a0.w & Sendo.b" shows how things internally work:

           tst.w      a0
           sne.b      -(sp)
           tst.b      Sendo
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)
           move.l     (-6,sp),d0

(the final result is available at the top of the stack, but will be automatically handled by generated code).


ARGUMENTS SIZES

The size used in comparisons is the one of the first register or, when there is no register, of the first argument.
Code produced for "Hanamichi.w = Kaede.b":

           move.l     d0,(-6,sp)
           move.w     Hanamichi,d0
            cmp.w     Kaede,d0
           seq.b      -(sp)
           move.l     (-4,sp),d0

Code produced for "d5.b = Haruko.l" or "Haruko.l = d5.b":

            cmp.b     Haruko,d5
           seq.b      -(sp)

Address registers sizes have priority over sizes of other operands.


EVALUATION ORDER

The boolexpr type syntax allows expressions of this kind: "d0 | d1 & d2" - which operator is applied first? Let us look at the resulting code:

           tst.l      d0               ;test d0
           sne.b      -(sp)
           tst.l      d1               ;then d1
           sne.b      -(sp)
           tst.l      d2               ;and finally d2
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)          ;d2 & d1
           move.l     (-6,sp),d0
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
            or.b      d0,(sp)          ;{d2 & d1} | d0
           move.l     (-6,sp),d0

This is not because & has higher priority than |, but simply because of the way ESA processes the source; in fact, by changing the order of the operators ("d0 & d1 | d2"), we get the same behaviour (but the result, like the expression, is not the same):

           tst.l      d0               ;test d0
           sne.b      -(sp)
           tst.l      d1               ;then d1
           sne.b      -(sp)
           tst.l      d2               ;and finally d2
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
            or.b      d0,(sp)          ;d2 | d1
           move.l     (-6,sp),d0
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)          ;{d2 | d1} & d0
           move.l     (-6,sp),d0

To control the evaluation order, it is possible to group operations with curly braces - and it is indeed strongly suggested to make use of them wherever there would be ambiguity. F.ex. "{d0 | d1} & d2":

           tst.l      d0               ;test d0
           sne.b      -(sp)
           tst.l      d1               ;then d1
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
            or.b      d0,(sp)          ;d1 | d0
           move.l     (-6,sp),d0
           tst.l      d2               ;test d2
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)          ;d2 & {d1 | d0}
           move.l     (-6,sp),d0

Note that braces have been preferred to the more stylish parentheses because the latter would introduce unsolvable ambiguities - f.ex. "~(a0)" could mean both a) logical complement of the data pointed to by a0; b) logical complement of the value stored in a0.

Comparison operators have higher priority over logical operators - "d0 < d1 & d2" is compiled as:

            cmp.l     d1,d0            ;first, execute comparison
           slt.b      -(sp)            ;d0 < d1
           tst.l      d2               ;then, test d2
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)          ;{d0 < d1} & d2
           move.l     (-6,sp),d0

(Note that evaluating "d1 & d2" first would have made no sense.)

The logical negation operator has less priority than the other operators, so "~d0 | d1" is seen as "~{d0 | d1}":

           tst.l      d0
           seq.b      -(sp)            ;~d0
           tst.l      d1
           seq.b      -(sp)            ;~d1
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)          ;{~d0} & {~d1}
           move.l     (-6,sp),d0

To restrict the negation to just d0, the code must be "{~d0} | d1":

           tst.l      d0
           seq.b      -(sp)            ;~d0
           tst.l      d1
           sne.b      -(sp)            ;d1
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
            or.b      d0,(sp)          ;{~d0} | d1
           move.l     (-6,sp),d0


CONDITION CODES

ESA offers direct support for checking the ccr. F.ex., let us suppose we want to be sure that an arithmetic operation did not generate an error; normally we would write:

           divu.w     d0,d1            ;perform division
           bvs        .mulerr          ;take care of overflow
           ...                         ;continue calculations

but we can also write:

           divu.w     d0,d1            ;perform division
           when vs                     ;if overflow
            ...                        ;take care of overflow
           ewhen                       ;continue calculations

Since condition codes are part of the boolexpr type, also more complex checks are possible:

           add.l      d0,d1            ;perform addition
           when cs | mi                ;if negative result or bit #31 shifted out
            ...                        ;do some additional operations
           ewhen

Syntactically, condition codes can be specified anywhere inside expressions, but indeed the ccr must be checked before anything else, because it is modified by the "extra" operations generated by ESA. F.ex., a sound check would be:

           subq.l     #8,d0
           when.s mi & d1
            moveq.l   #0,d0
           ewhen

which is compiled as:

           subq.l     #8,d0
           smi.b      -(sp)            ;the ccr holds the flags resulting from subq
           tst.l      d1
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)
           move.l     (-6,sp),d0
           tst.b      (sp)+
           beq.s      .0000000
           moveq.l    #0,d0
.0000000

whereas:

           subq.l     #8,d0
           when.s d1 & mi
            moveq.l   #0,d0
           ewhen

would yield uncorrect code, as the resulting listing shows:

           subq.l     #8,d0
           tst.l      d1
           sne.b      -(sp)
           smi.b      -(sp)            ;the ccr flags derive from tst, not subq
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)
           move.l     (-6,sp),d0
           tst.b      (sp)+
           beq.s      .0000000
           moveq.l    #0,d0
.0000000

Note that variables should never be named like condition codes, as they would be treated as codes.


STACK USAGE

As shown by the examples above, generated code uses the stack a lot in order to perform its job leaving the registers intact.

A consequence is that, like for condition codes, checks on the contents of the stack are guaranteed to succeed only if done before anything else - f.ex., "(sp)+ & Spike" is OK:

           tst.l      (sp)+            ;check immediately performed
           sne.b      -(sp)
           tst.l      Spike
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)
           move.l     (-6,sp),d0
           move.b     (sp)+,d7

whereas "Spike & (sp)+" returns an erroneous result:

           tst.l      Spike
           sne.b      -(sp)            ;decrements sp by 2
           tst.l      (sp)+            ;check done on wrong data
           sne.b      -(sp)
           move.l     d0,(-4,sp)
           move.b     (sp)+,d0
           and.b      d0,(sp)
           move.l     (-6,sp),d0
           move.b     (sp)+,d7

Another consequence is that the programmer cannot do dirty tricks with the stack because ESA itself has to resort to them:

           move.l     d5,(-8,sp)
           ...
           bool flags&state,d0
           ...
           move.l     (-8,sp),d5

is compiled as

           move.l     d5,(-8,sp)
           ...
           tst.l      flags
           sne.b      -(sp)            ;decrements sp by 2
           tst.l      state
           sne.b      -(sp)            ;decrements sp by 2
           move.l     d0,(-4,sp)       ;overwrites d5 value
           move.b     (sp)+,d0
           and.b      d0,(sp)
           move.l     (-6,sp),d0
           move.b     (sp)+,d0
           ...
           move.l     (-8,sp),d5

which, evidently, does not work.

The last consequence is that boolean expressions must be used carefully in supervisor mode: f.ex., if during the evaluation of a boolean expression in an interrupt handler another interrupt with higher priority occurs, the pre-stacked values may be overwritten by the stack frame or the code of the other handler. This sort of problem is even worse on CPUs that do not have separate isp and msp registers.


CODE EFFICIENCY

The code obtained when arguments are registers is more efficient, as stack usage can be reduced or eliminated altogether.
Code produced for "bool Ronzaman<d1,d0":

            cmp.l     Ronzaman,d1
            sgt.b     d0

Code produced for "bool a5.w»=Suppaman,result":

           cmpa.w     Suppaman,a5
           shs.b      result

Code produced for "bool Suppaman.b»=Ronzaman,result":

           move.l     d0,(-6,sp)
           move.b     Suppaman,d0
            cmp.b     Ronzaman,d0
           shs.b      -(sp)
           move.l     (-4,sp),d0
           move.b     (sp)+,result

Although boolean expressions are treated in a smart way, it is inevitable - because of the necessity of leaving registers intact - that the code produced for complex ones results somewhat ugly and inefficient: if speed is really needed, hand-written code is preferable.



home