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