Skip to content

[mlir][dxsa] Add BinaryWriter to translate from MLIR to DXSA#113

Open
asavonic wants to merge 10 commits into
dxsa-mlirfrom
dxsa-mlir-writer
Open

[mlir][dxsa] Add BinaryWriter to translate from MLIR to DXSA#113
asavonic wants to merge 10 commits into
dxsa-mlirfrom
dxsa-mlir-writer

Conversation

@asavonic
Copy link
Copy Markdown
Contributor

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.

@asavonic asavonic requested a review from tagolog April 27, 2026 13:49
@@ -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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can probably introduce --verify-roundtrip, similar to what mlir-op does for normal MLIR

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I'll see how to add an option like this.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
Region &region = source.getRegion();
if (!region.hasOneBlock()) {
emitError(region.getLoc(), "region should contain only one block");
return failure();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! I missed that.


for (auto &op : region.front()) {
if (auto inst = dyn_cast<dxsa::Instruction>(op)) {
if (failed(emitInstruction(inst))) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, that is expected. These patterns only propagate an error up.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
return failure();
}

if (auto operand = dyn_cast<dxsa::Operand>(*op)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may probably want to use TypeSwitch here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added TypeSwitch here and for indices as well.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
Comment on lines +195 to +201
if (!index) {
emitError(value.getLoc(), "index must be defined");
return failure();
}

FailureOr<uint32_t> repr = getIndexRepresentation(index);
if (failed(repr)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really a "normal" error"? How can we have value out of the thin air?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be enforced by IR then. I mean, if we're having graph regions of SSA dominance.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced these errors by assertions.
All instructions are in a builtin module op, which cannot have block arguments.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
return failure();
}

if (auto indexImm = dyn_cast<dxsa::IndexImm>(*index)) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above wrt TypeSwitch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
// 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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
// 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) {
Copy link
Copy Markdown
Contributor

@asl asl Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you might probably want to grab things by const reference in order not to copy every APInt here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
}

buffer.push_back(token);
for (uint32_t v : values) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

llvm::append_range(buffer, values).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@asavonic
Copy link
Copy Markdown
Contributor Author

Thanks a lot Anton! Let me know if I can improve anything else.
We have some of these problems in BinaryParser as well. I'll fix them separately.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
} 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");
Copy link
Copy Markdown
Contributor

@asl asl Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above. You can probably also TypeSwitch here with unreachable on default case.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Only IntegerType is possible here, so added cast<IntegerType> and an assert to only allow 32- and 64- bit integers.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
// 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());
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Comment thread mlir/lib/Target/DXSA/BinaryWriter.cpp Outdated
return emitError(index.getLoc(), "index must be defined");
}

auto operand = dyn_cast<dxsa::Operand>(*def);
Copy link
Copy Markdown
Contributor

@asl asl Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could probably combine the checks into dyn_cast_if_present? Or just cast depending on IR invariants. Same everywhere else.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. We can just cast it.

asavonic added 6 commits May 9, 2026 00:02
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.
@asavonic asavonic force-pushed the dxsa-mlir-writer branch from 2d21ed3 to 6657041 Compare May 8, 2026 15:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants