How to choose BooleanContent flavor?

There are three “flavors”: UndefinedBooleanContent, ZeroOrOneBooleanContent and ZeroOrNegativeOneBooleanContent.

The semantics is clear for me. What isn’t clear is how to choose which one is the best for my target.
Does it really depends on the ISA or is it just a matter of convenience? It seems that the optimized code should be the same whichever flavor I choose, is that correct?

Suppose my target can cheaply materialize “all ones” and “all zeros” values, but materializing “1” requires two instruction (materialize “all ones” and ‘neg’ or materialize “all zeros” and ‘not’). If I choose ZeroOrNegativeOne and I have e.g. ‘i32 select_cc x, y, i32 true, i32 false’, it can be lowered into three instructions (two for constant materialization and one the select itself). If the resulting boolean value is returned from the function, and the function return value is ‘zext i1’, I still need another instructions (neg) to normalize the result of the select. The ‘neg’ can be hoisted through the select to ‘y’ to reduce critical path length. If I choose ZeroOrOne, it will still be lowered to the same four instructions (this time I don’t need to hoist ‘neg’, it is already in place).

If the function had ‘sext i1’ instead of ‘zext i1’, that would save the ‘neg’. Does it mean that the choice depends on the ABI rather on the ISA?

It’s not really a fundamental property of the ISA or the ABI; it just determines what form other SelectionDAG instructions expect. On most targets, though, there’s one choice that’s obviously more efficient. (This might depend on the ABI.)

@efriedma-quic Thanks for the quick reply.

How can I figure out which one is more efficient for my target? What should I take in consideration?

Some instruction/instruction sequence which produces a boolean in a particular form, some instruction which consumes a boolean in a particular form, some ABI that requires a particular form, would make the decision obvious. If none of these lead to an obvious choice, I’d suggest ZeroOrOneBooleanContent; it has the most testing since it’s what most in-tree targets use.

1 Like

Thanks for these words, it helped me to gather my thoughts.

From the point of view of the ISA, there does not seem to be much of a difference. The target does not have instructions that expect a boolean “true” as all ones or else. It is either zero or non-zero. I.e. the target does not have boolean values except for status flags, which are equally expensive for materialization (except the carry flag, which can be cheaply materialized to zero-or-one value).

There seem to be bit of a contradiction: if a boolean is the result of a comparison, zero-or-one seems to be a slightly better choice; otherwise zero-or-negative-one looks more preferable. But since a boolean value is typically a result of a comparison (is it?), I should probably stick to zero-or-one flavor, because it is a bit cheaper to materialize (when the comparison is unsigned).
From the point of view of the ABI, it does not seem to matter which flavor I chose. The ABI does not require sign- or zero- extension of boolen values.

So, the main criterion in my case is whether boolean values are more frequently materialized as a result of a comparison or by assigning a constant value.

There are, however, cases like A && B || C && D, which will result in a bunch of branches (there is no select instruction) mixed with constant boolean values. With that in mind, zero-or-negative-one sounds better :frowning: