diff --git a/mlir/include/mlir/IR/BuiltinOps.h b/mlir/include/mlir/IR/BuiltinOps.h --- a/mlir/include/mlir/IR/BuiltinOps.h +++ b/mlir/include/mlir/IR/BuiltinOps.h @@ -17,6 +17,7 @@ #include "mlir/IR/OwningOpRef.h" #include "mlir/IR/SymbolTable.h" #include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/CastInterfaces.h" #include "llvm/Support/PointerLikeTypeTraits.h" //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/IR/BuiltinOps.td b/mlir/include/mlir/IR/BuiltinOps.td --- a/mlir/include/mlir/IR/BuiltinOps.td +++ b/mlir/include/mlir/IR/BuiltinOps.td @@ -17,6 +17,7 @@ include "mlir/IR/BuiltinDialect.td" include "mlir/IR/SymbolInterfaces.td" include "mlir/Interfaces/CallInterfaces.td" +include "mlir/Interfaces/CastInterfaces.td" // Base class for Builtin dialect ops. class Builtin_Op traits = []> : @@ -220,4 +221,49 @@ let assemblyFormat = "attr-dict"; } +//===----------------------------------------------------------------------===// +// UnrealizedConversionCastOp +//===----------------------------------------------------------------------===// + +def UnrealizedConversionCastOp : Builtin_Op<"unrealized_conversion_cast", [ + DeclareOpInterfaceMethods + ]> { + let summary = "An unrealized conversion from one set of types to another"; + let description = [{ + An `unrealized_conversion_cast` operation represents an unrealized + conversion from one set of types to another, that is used to enable the + inter-mixing of different type systems. This operation should not be + attributed any special representational or execution semantics, and is + generally only intended to be used to satisfy the temporary intermixing of + type systems during the conversion of one type system to another. + + This operation may produce results of arity 1-N, and accept as input + operands of arity 0-N. + + Example: + + ```mlir + // An unrealized 0-1 conversion. + %result = unrealized_conversion_cast to !bar.lowered_type + + // An unrealized 1-1 conversion. + %result1 = unrealized_conversion_cast(%operand : !foo.type) to !bar.lowered_type + + // An unrealized 1-N conversion. + %results2:2 = unrealized_conversion_cast(%operand : !foo.type) to !foo.type, !bar.lowered_type + + // An unrealized N-1 conversion. + %result3 = unrealized_conversion_cast(%operand, %operand : !foo.type, !foo.type) to !bar.lowered_type + ``` + }]; + + let arguments = (ins Variadic:$inputs); + let results = (outs Variadic:$outputs); + let assemblyFormat = [{ + (`(` $inputs^ `:` type($inputs) `)`)? `to` type($outputs) attr-dict + }]; + let hasCanonicalizer = 1; + let hasFolder = 1; +} + #endif // BUILTIN_OPS diff --git a/mlir/lib/IR/BuiltinDialect.cpp b/mlir/lib/IR/BuiltinDialect.cpp --- a/mlir/lib/IR/BuiltinDialect.cpp +++ b/mlir/lib/IR/BuiltinDialect.cpp @@ -18,6 +18,7 @@ #include "mlir/IR/BuiltinTypes.h" #include "mlir/IR/FunctionImplementation.h" #include "mlir/IR/OpImplementation.h" +#include "mlir/IR/PatternMatch.h" #include "llvm/ADT/MapVector.h" using namespace mlir; @@ -236,6 +237,60 @@ return success(); } +//===----------------------------------------------------------------------===// +// UnrealizedConversionCastOp +//===----------------------------------------------------------------------===// + +void UnrealizedConversionCastOp::getCanonicalizationPatterns( + OwningRewritePatternList &results, MLIRContext *context) { + /// Fold dead conversions that no longer have uses. + /// `UnrealizedConversionCastOp` does not provide any information about + /// effects (purposefully to remain opaque to users), so we need an explicit + /// canonicalization to erase it if unused. + struct SimplifyDeadOp : public OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + + LogicalResult matchAndRewrite(UnrealizedConversionCastOp op, + PatternRewriter &rewriter) const override { + if (op.use_empty()) { + rewriter.eraseOp(op); + return success(); + } + return failure(); + } + }; + + results.insert(context); +} + +LogicalResult +UnrealizedConversionCastOp::fold(ArrayRef attrOperands, + SmallVectorImpl &foldResults) { + OperandRange operands = inputs(); + if (operands.empty()) + return failure(); + + // Check that the input is a cast with results that all feed into this + // operation, and operand types that directly match the result types of this + // operation. + ResultRange results = outputs(); + Value firstInput = operands.front(); + auto inputOp = firstInput.getDefiningOp(); + if (!inputOp || inputOp.getResults() != operands || + inputOp.getOperandTypes() != results.getTypes()) + return failure(); + + // If everything matches up, we can fold the passthrough. + foldResults.append(inputOp->operand_begin(), inputOp->operand_end()); + return success(); +} + +bool UnrealizedConversionCastOp::areCastCompatible(TypeRange inputs, + TypeRange outputs) { + // `UnrealizedConversionCastOp` is agnostic of the input/output types. + return true; +} + //===----------------------------------------------------------------------===// // TableGen'd op method definitions //===----------------------------------------------------------------------===// diff --git a/mlir/lib/IR/CMakeLists.txt b/mlir/lib/IR/CMakeLists.txt --- a/mlir/lib/IR/CMakeLists.txt +++ b/mlir/lib/IR/CMakeLists.txt @@ -37,6 +37,7 @@ MLIRBuiltinOpsIncGen MLIRBuiltinTypesIncGen MLIRCallInterfacesIncGen + MLIRCastInterfacesIncGen MLIROpAsmInterfaceIncGen MLIRRegionKindInterfaceIncGen MLIRSymbolInterfacesIncGen diff --git a/mlir/test/Dialect/Builtin/canonicalize.mlir b/mlir/test/Dialect/Builtin/canonicalize.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Dialect/Builtin/canonicalize.mlir @@ -0,0 +1,25 @@ +// RUN: mlir-opt %s -canonicalize | FileCheck %s + +//===----------------------------------------------------------------------===// +// UnrealizedConversionCastOp +//===----------------------------------------------------------------------===// + +// Test folding conversion casts feeding into other casts. +// CHECK-LABEL: func @multiple_conversion_casts +// CHECK-SAME: %[[ARG0:.*]]: i32, %[[ARG1:.*]]: +func @multiple_conversion_casts(%arg0: i32, %arg1: i32) -> (i32, i32) { + // CHECK-NOT: unrealized_conversion_cast + // CHECK: return %[[ARG0]], %[[ARG1]] + %inputs:2 = unrealized_conversion_cast (%arg0, %arg1 : i32, i32) to i64, i64 + %outputs:2 = unrealized_conversion_cast (%inputs#0, %inputs#1 : i64, i64) to i32, i32 + return %outputs#0, %outputs#1 : i32, i32 +} + +// CHECK-LABEL: func @multiple_conversion_casts +func @multiple_conversion_casts_failure(%arg0: i32, %arg1: i32, %arg2: i64) -> (i32, i32) { + // CHECK: unrealized_conversion_cast + // CHECK: unrealized_conversion_cast + %inputs:2 = unrealized_conversion_cast (%arg0, %arg1 : i32, i32) to i64, i64 + %outputs:2 = unrealized_conversion_cast (%arg2, %inputs#1 : i64, i64) to i32, i32 + return %outputs#0, %outputs#1 : i32, i32 +} diff --git a/mlir/test/Dialect/Builtin/invalid.mlir b/mlir/test/Dialect/Builtin/invalid.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Dialect/Builtin/invalid.mlir @@ -0,0 +1,11 @@ +// RUN: mlir-opt %s -split-input-file -verify-diagnostics + +//===----------------------------------------------------------------------===// +// UnrealizedConversionCastOp +//===----------------------------------------------------------------------===// + +// expected-error@+1 {{expected at least one result for cast operation}} +"unrealized_conversion_cast"() : () -> () + +// ----- + diff --git a/mlir/test/Dialect/Builtin/ops.mlir b/mlir/test/Dialect/Builtin/ops.mlir new file mode 100644 --- /dev/null +++ b/mlir/test/Dialect/Builtin/ops.mlir @@ -0,0 +1,19 @@ +// RUN: mlir-opt %s -allow-unregistered-dialect | mlir-opt -allow-unregistered-dialect + +//===----------------------------------------------------------------------===// +// UnrealizedConversionCastOp +//===----------------------------------------------------------------------===// + +%operand = "foo.op"() : () -> !foo.type + +// An opaque 0-1 cast. +%result = unrealized_conversion_cast to !bar.lowered_type + +// An opaque 1-1 cast. +%result1 = unrealized_conversion_cast(%operand : !foo.type) to !bar.lowered_type + +// An opaque 1-N cast. +%results2:2 = unrealized_conversion_cast(%operand : !foo.type) to !foo.type, !bar.lowered_type + +// An opaque N-1 cast. +%result3 = unrealized_conversion_cast(%operand, %operand : !foo.type, !foo.type) to !bar.lowered_type