Consider, for example, ARM’s predicate operand:
def pred : PredicateOperand<OtherVT, (ops i32imm, i32imm),
(ops (i32 14), (i32 zero_reg))> {
let PrintMethod = "printPredicateOperand";
let ParserMatchClass = CondCodeOperand;
let DecoderMethod = "DecodePredicateOperand";
}
Or addrmode5
class AddrMode5 : MemOperand,
ComplexPattern<i32, 2, "SelectAddrMode5", []> {
let EncoderMethod = "getAddrMode5OpValue";
let DecoderMethod = "DecodeAddrMode5Operand";
let ParserMatchClass = AddrMode5AsmOperand;
let MIOperandInfo = (ops GPR:$base, i32imm);
}
def addrmode5 : AddrMode5 {
let PrintMethod = "printAddrMode5Operand<false>";
}
These operands have multiple operands in MIOperandInfo composing the whole.
- pred (i32imm, i32imm)
- addrmode5 (GPR, i32imm)
‘i32imm’ is defined as type OPERAND_IMMEDIATE, but InstrInfoEmitter::GetOperandInfo emits the type OPERAND_UNKNOWN for the index associated with the i32imm in the flattened operand list (–gen-instr-info).
This seems to be expected behavior. Consider ‘pred’ above. ‘zero_reg’, a register, is a default value, but i32imm, an immediate, is the operand type. I would have expected the machine code verifier to flag an error when it saw $noreg in the instruction where an immediate is expected, but because the operand type is UNKNOWN instead of IMMEDIATE, the error is skipped.
Digging a bit deeper, I found that InstrInfoEmitter.cpp assigns the type of each sub-operand to the containing operand.
Is there an ambiguous situation that is being avoided by such a classification? Assigning ‘correct’ types all the time seems to be a non-starter, as both operands above start break in multiple places during normal machine code verification, such as when addrmode5’s first operand receives a constant pool entry rather than a register for its first operand.
My current experiment replicates some code from CGIOperandList which inspects a record to construct the operand namespace and type.