TableGen: OperandType of nested operands in instructions

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.

I haven’t looked at this feature in a while, but last I did I thought it was pretty broken. I’d expect the expanded operands to have the correct types. Just because you see failures from this doesn’t mean it’s wrong; there’s a lot of broken tablegen relying on however things happen to work now.

I filed this related issue long ago, not sure if anything’s changed with this since: TableGen errors if operand with suboperands used without repeating type in output pattern · Issue #21913 · llvm/llvm-project · GitHub