From e1806237a9ca2bd984b3bae98d3dab8478e457e7 Mon Sep 17 00:00:00 2001 From: Fabian Mora Date: Fri, 30 Jan 2026 07:43:57 -0500 Subject: [PATCH 1/2] Add opaque inst Signed-off-by: Fabian Mora --- .../aster/Dialect/AMDGCN/IR/AMDGCNAttrs.td | 46 ++++++++ include/aster/Dialect/AMDGCN/IR/AMDGCNOps.td | 60 ++++++++++ lib/Dialect/AMDGCN/IR/AMDGCN.cpp | 105 ++++++++++++++++++ test/Dialect/AMDGCN/invalid.mlir | 28 +++++ test/Dialect/AMDGCN/ops.mlir | 34 ++++++ 5 files changed, 273 insertions(+) diff --git a/include/aster/Dialect/AMDGCN/IR/AMDGCNAttrs.td b/include/aster/Dialect/AMDGCN/IR/AMDGCNAttrs.td index 64c5fd51..79f191fd 100644 --- a/include/aster/Dialect/AMDGCN/IR/AMDGCNAttrs.td +++ b/include/aster/Dialect/AMDGCN/IR/AMDGCNAttrs.td @@ -236,6 +236,52 @@ def GridDimArgAttr : AMDGCN_Attr<"GridDimArg", "grid_dim_arg", [ }]; } +//===----------------------------------------------------------------------===// +// OpaqueModifier attribute +//===----------------------------------------------------------------------===// + +/// AMDGCN opaque modifier attribute. +def OpaqueModifierAttr : AMDGCN_Attr<"OpaqueModifier", "opaque_modifier"> { + let summary = "AMDGCN opaque modifier attribute"; + let description = [{ + Attribute representing an opaque modifier with a string value. + + Example: + + ```mlir + #amdgcn.opaque_modifier<"neg"> + ``` + }]; + let parameters = (ins StringRefParameter<>:$value); + let assemblyFormat = "`<` $value `>`"; +} + +//===----------------------------------------------------------------------===// +// ModifiersAttr attribute +//===----------------------------------------------------------------------===// + +def ModifiersAttr + : ArrayOfAttr { + let summary = "AMDGCN modifiers list attribute"; + let description = [{ + Attribute representing a list of opaque modifiers. + + Example: + + ```mlir + #amdgcn.modifiers<[#amdgcn.opaque_modifier<"neg">, #amdgcn.opaque_modifier<"abs">]> + ``` + }]; + let constBuilderCall = [{ + $_builder.getAttr(ArrayRef($0)) + }]; + let defaultValue = "{}"; + let assemblyFormat = [{ + `<`(`[` $value^ `]`)? `>` + }]; +} + //===----------------------------------------------------------------------===// // Kernel arguments attribute //===----------------------------------------------------------------------===// diff --git a/include/aster/Dialect/AMDGCN/IR/AMDGCNOps.td b/include/aster/Dialect/AMDGCN/IR/AMDGCNOps.td index 00dbc2f9..193a5cf4 100644 --- a/include/aster/Dialect/AMDGCN/IR/AMDGCNOps.td +++ b/include/aster/Dialect/AMDGCN/IR/AMDGCNOps.td @@ -670,6 +670,66 @@ def AMDGCN_WaitOp : AMDGCN_Op<"wait", [ let hasCanonicalizeMethod = 1; } +//===----------------------------------------------------------------------===// +// OpaqueOp +//===----------------------------------------------------------------------===// + +def AMDGCN_OpaqueOp : AMDGCN_Op<"opaque", [ + AMDGCNInstOpInterface, + AttrSizedOperandSegments + ]> { + let summary = "AMDGCN opaque instruction"; + let description = [{ + An opaque instruction that can represent any AMDGCN instruction. + This is useful for representing instructions that are not yet fully + modeled in the dialect. + + The `mnemonic` attribute specifies the instruction mnemonic. + The `in_mask` is a dense bool array where `true` signifies the + operand appears in the operand list and `false` that the operand is off. + If all values in `in_mask` are `true`, it is omitted from the assembly. + + Example: + ```mlir + %result = amdgcn.opaque "v_add_f32" outs(%dst) ins(%src0, %src1) + : (!amdgcn.vgpr<10>, !amdgcn.vgpr<11>, !amdgcn.vgpr<12>) -> !amdgcn.vgpr<10> + ``` + }]; + let arguments = (ins + StrAttr:$mnemonic, + Variadic:$outs, + Variadic:$ins, + DenseBoolArrayAttr:$in_mask, + OptionalAttr:$modifiers + ); + let results = (outs Variadic:$results); + let assemblyFormat = [{ + $mnemonic (`outs` `(` $outs^ `)`)? (`ins` `(` $ins^ `)`)? + custom(ref($ins), $in_mask) custom($modifiers) + attr-dict `:` functional-type(operands, results) + }]; + let hasVerifier = 1; + let extraClassDeclaration = [{ + //===------------------------------------------------------------------===// + // InstOpInterface Methods + //===------------------------------------------------------------------===// + + /// Get the opcode of the instruction. + InstAttr getOpcodeAttr() { + // Opaque ops don't have a typed opcode, return null. + return InstAttr(); + } + /// Get the instruction output operands. + MutableArrayRef getInstOutsMutable() { + return getOutsMutable(); + } + /// Get the instruction input operands. + MutableArrayRef getInstInsMutable() { + return getInsMutable(); + } + }]; +} + //===----------------------------------------------------------------------===// // AMDGCN Target Operations //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/AMDGCN/IR/AMDGCN.cpp b/lib/Dialect/AMDGCN/IR/AMDGCN.cpp index 5f10af23..e044e51c 100644 --- a/lib/Dialect/AMDGCN/IR/AMDGCN.cpp +++ b/lib/Dialect/AMDGCN/IR/AMDGCN.cpp @@ -934,6 +934,111 @@ LogicalResult LibraryOp::verify() { [&]() { return emitError(); }); } +//===----------------------------------------------------------------------===// +// OpaqueOp +//===----------------------------------------------------------------------===// + +/// Check if all values in the mask are true. +static bool isAllTrue(ArrayRef mask) { + return llvm::all_of(mask, [](bool v) { return v; }); +} + +/// Parse the optional ins_mask for OpaqueOp. +/// Format: (`ins_mask` $in_mask)? +/// If not present, creates an all-true mask with size matching the ins +/// operands. +static ParseResult +parseOpaqueInsMask(OpAsmParser &parser, + ArrayRef ins, + DenseBoolArrayAttr &inMask) { + // Try to parse the optional ins_mask keyword. + if (succeeded(parser.parseOptionalKeyword("ins_mask"))) { + if (parser.parseAttribute(inMask)) + return failure(); + } else { + // No ins_mask provided, create a default mask of all true. + SmallVector defaultMask(ins.size(), true); + inMask = DenseBoolArrayAttr::get(parser.getContext(), defaultMask); + } + return success(); +} + +/// Print the optional ins_mask for OpaqueOp. +/// Only prints if the mask is not all true. +static void printOpaqueInsMask(OpAsmPrinter &printer, Operation *op, + OperandRange ins, DenseBoolArrayAttr inMask) { + ArrayRef mask = inMask.asArrayRef(); + if (!isAllTrue(mask)) { + printer << " ins_mask "; + printer.printAttribute(inMask); + } +} + +/// Parse the optional modifiers for OpaqueOp. +/// Format: `modifiers` `(` $mod (`,` $mod)* `)` +static ParseResult parseOpaqueModifiers(OpAsmParser &parser, + ModifiersAttr &modifiers) { + // Try to parse the optional modifiers keyword. + if (failed(parser.parseOptionalKeyword("modifiers"))) + return success(); + + // Parse the opening parenthesis. + if (parser.parseLParen()) + return failure(); + + // Parse the list of modifiers. + SmallVector modList; + do { + std::string modStr; + if (parser.parseKeywordOrString(&modStr)) + return failure(); + modList.push_back(OpaqueModifierAttr::get(parser.getContext(), modStr)); + } while (succeeded(parser.parseOptionalComma())); + + // Parse the closing parenthesis. + if (parser.parseRParen()) + return failure(); + + modifiers = ModifiersAttr::get(parser.getContext(), modList); + return success(); +} + +/// Print the optional modifiers for OpaqueOp. +/// Format: `modifiers` `(` $mod (`,` $mod)* `)` +static void printOpaqueModifiers(OpAsmPrinter &printer, Operation *op, + ModifiersAttr modifiers) { + if (!modifiers || modifiers.empty()) + return; + + printer << " modifiers("; + llvm::interleaveComma(modifiers.getValue(), printer, [&](Attribute attr) { + auto mod = cast(attr); + printer << mod.getValue(); + }); + printer << ")"; +} + +LogicalResult OpaqueOp::verify() { + // Check that all outs are AMDGCN register types. + for (Value out : getOuts()) { + if (!isa(out.getType())) + return emitOpError("expected all 'outs' operands to be AMDGCN register " + "types, but got ") + << out.getType(); + } + + // Check that the number of true elements in in_mask matches the number of + // ins operands. + ArrayRef mask = getInMask(); + size_t numTrue = llvm::count(mask, true); + if (numTrue != getIns().size()) + return emitOpError("expected the number of 'true' elements in 'in_mask' (") + << numTrue << ") to match the number of 'ins' operands (" + << getIns().size() << ")"; + + return success(); +} + //===----------------------------------------------------------------------===// // WaitOp //===----------------------------------------------------------------------===// diff --git a/test/Dialect/AMDGCN/invalid.mlir b/test/Dialect/AMDGCN/invalid.mlir index a2bc6480..50f9f7d4 100644 --- a/test/Dialect/AMDGCN/invalid.mlir +++ b/test/Dialect/AMDGCN/invalid.mlir @@ -118,3 +118,31 @@ func.func @offset_with_invalid_aligment() { %2 = amdgcn.alloc_lds 32 alignment 8 offset 33 return } + +// ----- + +//===----------------------------------------------------------------------===// +// OpaqueOp Verification +//===----------------------------------------------------------------------===// + +func.func @opaque_invalid_outs_type(%arg0: i32, %arg1: !amdgcn.vgpr<2>, %arg2: !amdgcn.vgpr<3>) -> i32 { + // expected-error @+1 {{expected all 'outs' operands to be AMDGCN register types, but got 'i32'}} + %0 = amdgcn.opaque "v_add_f32" outs(%arg0) ins(%arg1, %arg2) : (i32, !amdgcn.vgpr<2>, !amdgcn.vgpr<3>) -> i32 + return %0 : i32 +} + +// ----- + +func.func @opaque_in_mask_mismatch(%arg0: !amdgcn.vgpr<1>, %arg1: !amdgcn.vgpr<2>) -> !amdgcn.vgpr<1> { + // expected-error @+1 {{expected the number of 'true' elements in 'in_mask' (2) to match the number of 'ins' operands (1)}} + %0 = amdgcn.opaque "v_add_f32" outs(%arg0) ins(%arg1) ins_mask array : (!amdgcn.vgpr<1>, !amdgcn.vgpr<2>) -> !amdgcn.vgpr<1> + return %0 : !amdgcn.vgpr<1> +} + +// ----- + +func.func @opaque_in_mask_mismatch_zero(%arg0: !amdgcn.vgpr<1>, %arg1: !amdgcn.vgpr<2>, %arg2: !amdgcn.vgpr<3>) -> !amdgcn.vgpr<1> { + // expected-error @+1 {{expected the number of 'true' elements in 'in_mask' (0) to match the number of 'ins' operands (2)}} + %0 = amdgcn.opaque "v_add_f32" outs(%arg0) ins(%arg1, %arg2) ins_mask array : (!amdgcn.vgpr<1>, !amdgcn.vgpr<2>, !amdgcn.vgpr<3>) -> !amdgcn.vgpr<1> + return %0 : !amdgcn.vgpr<1> +} diff --git a/test/Dialect/AMDGCN/ops.mlir b/test/Dialect/AMDGCN/ops.mlir index 8812f195..423c1eab 100644 --- a/test/Dialect/AMDGCN/ops.mlir +++ b/test/Dialect/AMDGCN/ops.mlir @@ -213,3 +213,37 @@ func.func @lds_buffer_ops(%arg0: index, %arg1: index) { %2 = amdgcn.alloc_lds 32 offset 64 return } + +//===----------------------------------------------------------------------===// +// OpaqueOp Operations +//===----------------------------------------------------------------------===// + +func.func @opaque_basic(%arg0: !amdgcn.vgpr<1>, %arg1: !amdgcn.vgpr<2>, %arg2: !amdgcn.vgpr<3>) -> !amdgcn.vgpr<1> { + %0 = amdgcn.opaque "v_add_f32" outs(%arg0) ins(%arg1, %arg2) modifiers("offset(0)", neg) : (!amdgcn.vgpr<1>, !amdgcn.vgpr<2>, !amdgcn.vgpr<3>) -> !amdgcn.vgpr<1> + return %0 : !amdgcn.vgpr<1> +} + +func.func @opaque_no_outs(%arg0: !amdgcn.vgpr<1>, %arg1: !amdgcn.vgpr<2>) { + amdgcn.opaque "v_mov_b32" ins(%arg0, %arg1) : (!amdgcn.vgpr<1>, !amdgcn.vgpr<2>) -> () + return +} + +func.func @opaque_no_ins(%arg0: !amdgcn.vgpr<1>) -> !amdgcn.vgpr<1> { + %0 = amdgcn.opaque "v_nop" outs(%arg0) : (!amdgcn.vgpr<1>) -> !amdgcn.vgpr<1> + return %0 : !amdgcn.vgpr<1> +} + +func.func @opaque_with_ins_mask(%arg0: !amdgcn.vgpr<1>, %arg1: !amdgcn.vgpr<2>) -> !amdgcn.vgpr<1> { + %0 = amdgcn.opaque "v_add_f32" outs(%arg0) ins(%arg1) ins_mask array : (!amdgcn.vgpr<1>, !amdgcn.vgpr<2>) -> !amdgcn.vgpr<1> + return %0 : !amdgcn.vgpr<1> +} + +func.func @opaque_multiple_results(%arg0: !amdgcn.vgpr<1>, %arg1: !amdgcn.vgpr<2>) -> (!amdgcn.vgpr<1>) { + %0 = amdgcn.opaque "v_div_scale_f32" outs(%arg0) ins(%arg1) : (!amdgcn.vgpr<1>, !amdgcn.vgpr<2>) -> (!amdgcn.vgpr<1>) + return %0 : !amdgcn.vgpr<1> +} + +func.func @opaque_sgpr_and_vgpr(%arg0: !amdgcn.sgpr<1>, %arg1: !amdgcn.vgpr<2>, %arg2: !amdgcn.vgpr<3>) -> !amdgcn.sgpr<1> { + %0 = amdgcn.opaque "v_add_f32" outs(%arg0) ins(%arg1, %arg2) : (!amdgcn.sgpr<1>, !amdgcn.vgpr<2>, !amdgcn.vgpr<3>) -> !amdgcn.sgpr<1> + return %0 : !amdgcn.sgpr<1> +} From d54336ce686a06f57597b3dad697db5364e1c25a Mon Sep 17 00:00:00 2001 From: Fabian Mora Date: Fri, 30 Jan 2026 07:52:09 -0500 Subject: [PATCH 2/2] Add opaque inst translation Signed-off-by: Fabian Mora --- lib/Target/ASM/TranslateModule.cpp | 36 ++++++++++++++++++++++++++++++ test/Target/ASM/opaque.mlir | 15 +++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/Target/ASM/opaque.mlir diff --git a/lib/Target/ASM/TranslateModule.cpp b/lib/Target/ASM/TranslateModule.cpp index 81c8e7f8..02f5f649 100644 --- a/lib/Target/ASM/TranslateModule.cpp +++ b/lib/Target/ASM/TranslateModule.cpp @@ -61,6 +61,42 @@ static void printLSOffset(amdgcn::AsmPrinter &printer, AMDGCNInstOpInterface op, /// Prints the given instruction using the AsmPrinter. static llvm::LogicalResult printInstruction(amdgcn::AsmPrinter &printer, AMDGCNInstOpInterface op) { + if (auto opaque = dyn_cast(op.getOperation())) { + // Print: mnemonic outs ins modifiers + auto guard = printer.printMnemonic(opaque.getMnemonic()); + + // Print outs operands. + llvm::interleave( + opaque.getOuts(), printer.getStream(), + [&](Value out) { printer.printOperand(out); }, ","); + + bool requiresComma = !opaque.getOuts().empty(); + // Print ins operands - check in_mask: if true print operand, else "off". + int64_t insIdx = 0; + for (bool inMask : opaque.getInMask()) { + if (requiresComma) + printer.getStream() << ","; + requiresComma = true; + if (inMask) { + // Print the actual operand. + printer.printOperand(opaque.getIns()[insIdx++]); + continue; + } + printer.printKeyword("off"); + } + + // Print modifiers - each entry in the array literally. + if (std::optional> modifiers = + opaque.getModifiers(); + modifiers && !modifiers->empty()) { + printer.getStream() << " "; + auto printMod = [&](OpaqueModifierAttr mod) { + printer.getStream() << mod.getValue(); + }; + llvm::interleave(*modifiers, printer.getStream(), printMod, " "); + } + return success(); + } OpCode opcode = op.getOpcodeAttr().getValue(); assert(opcode != OpCode::Invalid && "invalid opcode in instruction"); if (_instPrinters.size() <= static_cast(opcode) || diff --git a/test/Target/ASM/opaque.mlir b/test/Target/ASM/opaque.mlir new file mode 100644 index 00000000..78e3280f --- /dev/null +++ b/test/Target/ASM/opaque.mlir @@ -0,0 +1,15 @@ +// RUN: aster-translate %s --mlir-to-asm | FileCheck %s + +// CHECK-LABEL:Module: mod +// CHECK: v_add_f32 v3, v2, v3 +// CHECK: v_add_f32 v3, v2, v3 offset(0) neg +amdgcn.module @mod target = #amdgcn.target isa = #amdgcn.isa { + amdgcn.kernel @opaque { + %0 = amdgcn.alloca : !amdgcn.vgpr<2> + %1 = amdgcn.alloca : !amdgcn.vgpr<3> + %2 = amdgcn.alloca : !amdgcn.vgpr<3> + opaque "v_add_f32" outs(%2) ins(%0, %1) : (!amdgcn.vgpr<3>, !amdgcn.vgpr<2>, !amdgcn.vgpr<3>) -> (!amdgcn.vgpr<3>) + opaque "v_add_f32" outs(%2) ins(%0, %1) modifiers("offset(0)", neg) : (!amdgcn.vgpr<3>, !amdgcn.vgpr<2>, !amdgcn.vgpr<3>) -> (!amdgcn.vgpr<3>) + amdgcn.end_kernel + } +}