[mlir][dxsa] Add BinaryWriter to translate from MLIR to DXSA#113
[mlir][dxsa] Add BinaryWriter to translate from MLIR to DXSA#113asavonic wants to merge 10 commits into
Conversation
| @@ -1,4 +1,8 @@ | |||
| // RUN: mlir-translate --import-dxsa-bin | FileCheck %s | |||
| // RUN: mlir-translate --export-dxsa-bin %s -o - | mlir-translate --import-dxsa-bin - | FileCheck %s | |||
There was a problem hiding this comment.
You can probably introduce --verify-roundtrip, similar to what mlir-op does for normal MLIR
There was a problem hiding this comment.
Good idea. I'll see how to add an option like this.
| Region ®ion = source.getRegion(); | ||
| if (!region.hasOneBlock()) { | ||
| emitError(region.getLoc(), "region should contain only one block"); | ||
| return failure(); |
There was a problem hiding this comment.
emitError produces InFlightDiagnostics that is implicitly convertible to LogicalResult. This could be simplified down to:
return emitError(region.getLoc() , "region should contain only one block");Ditto in other places.
There was a problem hiding this comment.
Great! I missed that.
|
|
||
| for (auto &op : region.front()) { | ||
| if (auto inst = dyn_cast<dxsa::Instruction>(op)) { | ||
| if (failed(emitInstruction(inst))) { |
There was a problem hiding this comment.
When doing something like this, please ensure that all paths of emitInstruction() produce meaningful diagnostics. Otherwise, you'll end with failed run w/o proper understanding what was wrong.
There was a problem hiding this comment.
Sure, that is expected. These patterns only propagate an error up.
| return failure(); | ||
| } | ||
|
|
||
| if (auto operand = dyn_cast<dxsa::Operand>(*op)) { |
There was a problem hiding this comment.
You may probably want to use TypeSwitch here.
There was a problem hiding this comment.
Added TypeSwitch here and for indices as well.
| if (!index) { | ||
| emitError(value.getLoc(), "index must be defined"); | ||
| return failure(); | ||
| } | ||
|
|
||
| FailureOr<uint32_t> repr = getIndexRepresentation(index); | ||
| if (failed(repr)) { |
There was a problem hiding this comment.
Is it really a "normal" error"? How can we have value out of the thin air?
There was a problem hiding this comment.
It is not a normal error. I suppose it can only happen if a value is a block argument, but we do not have control flow for this dialect. Do you suggest to make it an assertion?
There was a problem hiding this comment.
This should be enforced by IR then. I mean, if we're having graph regions of SSA dominance.
There was a problem hiding this comment.
Replaced these errors by assertions.
All instructions are in a builtin module op, which cannot have block arguments.
| return failure(); | ||
| } | ||
|
|
||
| if (auto indexImm = dyn_cast<dxsa::IndexImm>(*index)) { |
| // operands do not have indices. They are encoded as an operand | ||
| // followed by N immediate values for each component. | ||
| LogicalResult emitOperandImm(dxsa::OperandImm op) { | ||
| auto attr = dyn_cast<DenseIntElementsAttr>(op.getImm()); |
There was a problem hiding this comment.
do we have verify on dxsa::OperandImm that ensures proper type here? If not, then it should be added and / or type constraint added to the instruction definition.
There was a problem hiding this comment.
There is AnyAttrOf<[I32ElementsAttr, I64ElementsAttr]> constraint, but getImm returns mlir::Attribute, so we need to cast it here. Perhaps we can have an assertion instead, but I'm not sure if anything other than DenseIntElementsAttr is possible.
There was a problem hiding this comment.
here is how the things are defined:
class SignlessIntElementsAttr<int width> : IntElementsAttrBase<
CPred<"::llvm::cast<::mlir::DenseIntElementsAttr>($_self).getType()."
"getElementType().isSignlessInteger(" # width # ")">,
width # "-bit signless integer elements attribute"> {
// Note that this is only constructing scalar elements attribute.
let constBuilderCall = "::llvm::cast<::mlir::DenseIntElementsAttr>("
"::mlir::DenseElementsAttr::get("
"::mlir::RankedTensorType::get({}, $_builder.getIntegerType(" # width # ")), "
"::llvm::ArrayRef($0)))";
}
def I32ElementsAttr : SignlessIntElementsAttr<32>;
def I64ElementsAttr : SignlessIntElementsAttr<64>;
So it is always DenseIntElementsAttr and this is enforced by verifier. You should just do direct cast here, no check / error is needed.
| // encoded as is, and 64 bit immediates are split into high and | ||
| // low 32 bit parts. | ||
| SmallVector<uint32_t, 4> values; | ||
| for (APInt v : attr) { |
There was a problem hiding this comment.
you might probably want to grab things by const reference in order not to copy every APInt here.
| } | ||
|
|
||
| buffer.push_back(token); | ||
| for (uint32_t v : values) { |
There was a problem hiding this comment.
llvm::append_range(buffer, values).
|
Thanks a lot Anton! Let me know if I can improve anything else. |
| } else if (elementType.isInteger(64)) { | ||
| token |= ENCODE_D3D10_SB_OPERAND_TYPE(D3D10_SB_OPERAND_TYPE_IMMEDIATE64); | ||
| } else { | ||
| return emitError(op.getLoc(), "invalid immediate operand type"); |
There was a problem hiding this comment.
see above. You can probably also TypeSwitch here with unreachable on default case.
There was a problem hiding this comment.
Thank you. Only IntegerType is possible here, so added cast<IntegerType> and an assert to only allow 32- and 64- bit integers.
| // Emit an immediate index. Its type is encoded into the operand, so | ||
| // here we only emit the value as tokens. | ||
| LogicalResult emitIndexImm(dxsa::IndexImm op) { | ||
| auto attr = dyn_cast<IntegerAttr>(op.getImm()); |
There was a problem hiding this comment.
See above on cast thing. We can always assume that IR is valid, IR verifier enforces this. So, you'd assert instead of error out.
| return emitError(index.getLoc(), "index must be defined"); | ||
| } | ||
|
|
||
| auto operand = dyn_cast<dxsa::Operand>(*def); |
There was a problem hiding this comment.
You could probably combine the checks into dyn_cast_if_present? Or just cast depending on IR invariants. Same everywhere else.
There was a problem hiding this comment.
Done. We can just cast it.
BinaryWriter translates from an MLIR module in DXSA dialect into a DXSA binary. It is a reverse of what BinaryParser does. Current implementation only supports standard instructions, and needs to be extended to support custom instructions. Instruction table is moved into a separate file (InstrInfo.def), so it can be shared between Parser/Writer, which build different data structures from it. Parser goes from opcodes to mnemonics, and Writer is reversed. Tests are extended to run MLIR in roundtrip to verify both the Parser and Writer. We also compare binary output with input to make sure that we do not lose any data during translation.
BinaryWriter translates from an MLIR module in DXSA dialect into a DXSA binary. It is a reverse of what BinaryParser does.
Current implementation only supports standard instructions, and needs to be extended to support custom instructions.
Instruction table is moved into a separate file (InstrInfo.def), so it can be shared between Parser/Writer, which build different data structures from it. Parser goes from opcodes to mnemonics, and Writer is reversed.
Tests are extended to run MLIR in roundtrip to verify both the Parser and Writer. We also compare binary output with input to make sure that we do not lose any data during translation.