| 1 | //===- OpBuildGen.cpp - TableGen OpBuildGen Tests -------------------------===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | // |
| 9 | // Test TableGen generated build() methods on Operations. |
| 10 | // |
| 11 | //===----------------------------------------------------------------------===// |
| 12 | |
| 13 | #include "TestDialect.h" |
| 14 | #include "TestOps.h" |
| 15 | #include "mlir/IR/Attributes.h" |
| 16 | #include "mlir/IR/Builders.h" |
| 17 | #include "mlir/IR/BuiltinTypes.h" |
| 18 | #include "mlir/IR/Dialect.h" |
| 19 | #include "gmock/gmock.h" |
| 20 | #include <vector> |
| 21 | |
| 22 | namespace mlir { |
| 23 | |
| 24 | //===----------------------------------------------------------------------===// |
| 25 | // Test Fixture |
| 26 | //===----------------------------------------------------------------------===// |
| 27 | |
| 28 | static MLIRContext &getContext() { |
| 29 | static MLIRContext ctx; |
| 30 | ctx.getOrLoadDialect<test::TestDialect>(); |
| 31 | return ctx; |
| 32 | } |
| 33 | /// Test fixture for providing basic utilities for testing. |
| 34 | class OpBuildGenTest : public ::testing::Test { |
| 35 | protected: |
| 36 | OpBuildGenTest() |
| 37 | : ctx(getContext()), builder(&ctx), loc(builder.getUnknownLoc()), |
| 38 | i32Ty(builder.getI32Type()), f32Ty(builder.getF32Type()), |
| 39 | cstI32(builder.create<test::TableGenConstant>(loc, i32Ty)), |
| 40 | cstF32(builder.create<test::TableGenConstant>(loc, f32Ty)), |
| 41 | noAttrs(), attrStorage{builder.getNamedAttr(name: "attr0" , |
| 42 | val: builder.getBoolAttr(value: true)), |
| 43 | builder.getNamedAttr( |
| 44 | "attr1" , builder.getI32IntegerAttr(33))}, |
| 45 | attrs(attrStorage) {} |
| 46 | |
| 47 | // Verify that `op` has the given set of result types, operands, and |
| 48 | // attributes. |
| 49 | template <typename OpTy> |
| 50 | void verifyOp(OpTy &&concreteOp, std::vector<Type> resultTypes, |
| 51 | std::vector<Value> operands, |
| 52 | std::vector<NamedAttribute> attrs) { |
| 53 | ASSERT_NE(concreteOp, nullptr); |
| 54 | Operation *op = concreteOp.getOperation(); |
| 55 | |
| 56 | EXPECT_EQ(op->getNumResults(), resultTypes.size()); |
| 57 | for (unsigned idx : llvm::seq(Begin: 0U, End: op->getNumResults())) |
| 58 | EXPECT_EQ(op->getResult(idx).getType(), resultTypes[idx]); |
| 59 | |
| 60 | EXPECT_EQ(op->getNumOperands(), operands.size()); |
| 61 | for (unsigned idx : llvm::seq(Begin: 0U, End: op->getNumOperands())) |
| 62 | EXPECT_EQ(op->getOperand(idx), operands[idx]); |
| 63 | |
| 64 | EXPECT_EQ(op->getAttrs().size(), attrs.size()); |
| 65 | for (unsigned idx : llvm::seq<unsigned>(Begin: 0U, End: attrs.size())) |
| 66 | EXPECT_EQ(op->getAttr(attrs[idx].getName().strref()), |
| 67 | attrs[idx].getValue()); |
| 68 | |
| 69 | EXPECT_TRUE(mlir::succeeded(concreteOp.verify())); |
| 70 | concreteOp.erase(); |
| 71 | } |
| 72 | |
| 73 | template <typename OpTy> |
| 74 | void verifyOp(OpTy &&concreteOp, std::vector<Type> resultTypes, |
| 75 | std::vector<Value> operands1, std::vector<Value> operands2, |
| 76 | std::vector<NamedAttribute> attrs) { |
| 77 | ASSERT_NE(concreteOp, nullptr); |
| 78 | Operation *op = concreteOp.getOperation(); |
| 79 | |
| 80 | EXPECT_EQ(op->getNumResults(), resultTypes.size()); |
| 81 | for (unsigned idx : llvm::seq(Begin: 0U, End: op->getNumResults())) |
| 82 | EXPECT_EQ(op->getResult(idx).getType(), resultTypes[idx]); |
| 83 | |
| 84 | auto operands = llvm::to_vector(Range: llvm::concat<Value>(Ranges&: operands1, Ranges&: operands2)); |
| 85 | EXPECT_EQ(op->getNumOperands(), operands.size()); |
| 86 | for (unsigned idx : llvm::seq(Begin: 0U, End: op->getNumOperands())) |
| 87 | EXPECT_EQ(op->getOperand(idx), operands[idx]); |
| 88 | |
| 89 | EXPECT_EQ(op->getAttrs().size(), attrs.size()); |
| 90 | if (op->getAttrs().size() != attrs.size()) { |
| 91 | // Simple export where there is mismatch count. |
| 92 | llvm::errs() << "Op attrs:\n" ; |
| 93 | for (auto it : op->getAttrs()) |
| 94 | llvm::errs() << "\t" << it.getName() << " = " << it.getValue() << "\n" ; |
| 95 | |
| 96 | llvm::errs() << "Expected attrs:\n" ; |
| 97 | for (auto it : attrs) |
| 98 | llvm::errs() << "\t" << it.getName() << " = " << it.getValue() << "\n" ; |
| 99 | } else { |
| 100 | for (unsigned idx : llvm::seq<unsigned>(Begin: 0U, End: attrs.size())) |
| 101 | EXPECT_EQ(op->getAttr(attrs[idx].getName().strref()), |
| 102 | attrs[idx].getValue()); |
| 103 | } |
| 104 | |
| 105 | EXPECT_TRUE(mlir::succeeded(concreteOp.verify())); |
| 106 | concreteOp.erase(); |
| 107 | } |
| 108 | |
| 109 | protected: |
| 110 | MLIRContext &ctx; |
| 111 | OpBuilder builder; |
| 112 | Location loc; |
| 113 | Type i32Ty; |
| 114 | Type f32Ty; |
| 115 | OwningOpRef<test::TableGenConstant> cstI32; |
| 116 | OwningOpRef<test::TableGenConstant> cstF32; |
| 117 | |
| 118 | ArrayRef<NamedAttribute> noAttrs; |
| 119 | std::vector<NamedAttribute> attrStorage; |
| 120 | ArrayRef<NamedAttribute> attrs; |
| 121 | }; |
| 122 | |
| 123 | /// Test basic build methods. |
| 124 | TEST_F(OpBuildGenTest, BasicBuildMethods) { |
| 125 | // Test separate args, separate results build method. |
| 126 | auto op = builder.create<test::TableGenBuildOp0>(loc, i32Ty, *cstI32); |
| 127 | verifyOp(op, {i32Ty}, {*cstI32}, noAttrs); |
| 128 | |
| 129 | // Test separate args, collective results build method. |
| 130 | op = builder.create<test::TableGenBuildOp0>(loc, TypeRange{i32Ty}, *cstI32); |
| 131 | verifyOp(op, {i32Ty}, {*cstI32}, noAttrs); |
| 132 | |
| 133 | // Test collective args, collective params build method. |
| 134 | op = builder.create<test::TableGenBuildOp0>(loc, TypeRange{i32Ty}, |
| 135 | ValueRange{*cstI32}); |
| 136 | verifyOp(op, {i32Ty}, {*cstI32}, noAttrs); |
| 137 | |
| 138 | // Test collective args, collective results, non-empty attributes |
| 139 | op = builder.create<test::TableGenBuildOp0>(loc, TypeRange{i32Ty}, |
| 140 | ValueRange{*cstI32}, attrs); |
| 141 | verifyOp(op, {i32Ty}, {*cstI32}, attrs); |
| 142 | } |
| 143 | |
| 144 | /// The following 3 tests exercise build methods generated for operations |
| 145 | /// with a combination of: |
| 146 | /// |
| 147 | /// single variadic arg x |
| 148 | /// {single variadic result, non-variadic result, multiple variadic results} |
| 149 | /// |
| 150 | /// Specifically to test that ODS framework does not generate ambiguous |
| 151 | /// build() methods that fail to compile. |
| 152 | |
| 153 | /// Test build methods for an Op with a single varadic arg and a single |
| 154 | /// variadic result. |
| 155 | TEST_F(OpBuildGenTest, BuildMethodsSingleVariadicArgAndResult) { |
| 156 | // Test collective args, collective results method, building a unary op. |
| 157 | auto op = builder.create<test::TableGenBuildOp1>(loc, TypeRange{i32Ty}, |
| 158 | ValueRange{*cstI32}); |
| 159 | verifyOp(op, {i32Ty}, {*cstI32}, noAttrs); |
| 160 | |
| 161 | // Test collective args, collective results method, building a unary op with |
| 162 | // named attributes. |
| 163 | op = builder.create<test::TableGenBuildOp1>(loc, TypeRange{i32Ty}, |
| 164 | ValueRange{*cstI32}, attrs); |
| 165 | verifyOp(op, {i32Ty}, {*cstI32}, attrs); |
| 166 | |
| 167 | // Test collective args, collective results method, building a binary op. |
| 168 | op = builder.create<test::TableGenBuildOp1>(loc, TypeRange{i32Ty, f32Ty}, |
| 169 | ValueRange{*cstI32, *cstF32}); |
| 170 | verifyOp(op, {i32Ty, f32Ty}, {*cstI32, *cstF32}, noAttrs); |
| 171 | |
| 172 | // Test collective args, collective results method, building a binary op with |
| 173 | // named attributes. |
| 174 | op = builder.create<test::TableGenBuildOp1>( |
| 175 | loc, TypeRange{i32Ty, f32Ty}, ValueRange{*cstI32, *cstF32}, attrs); |
| 176 | verifyOp(op, {i32Ty, f32Ty}, {*cstI32, *cstF32}, attrs); |
| 177 | } |
| 178 | |
| 179 | /// Test build methods for an Op with a single varadic arg and a non-variadic |
| 180 | /// result. |
| 181 | TEST_F(OpBuildGenTest, BuildMethodsSingleVariadicArgNonVariadicResults) { |
| 182 | // Test separate arg, separate param build method. |
| 183 | auto op = |
| 184 | builder.create<test::TableGenBuildOp1>(loc, i32Ty, ValueRange{*cstI32}); |
| 185 | verifyOp(op, {i32Ty}, {*cstI32}, noAttrs); |
| 186 | |
| 187 | // Test collective params build method, no attributes. |
| 188 | op = builder.create<test::TableGenBuildOp1>(loc, TypeRange{i32Ty}, |
| 189 | ValueRange{*cstI32}); |
| 190 | verifyOp(op, {i32Ty}, {*cstI32}, noAttrs); |
| 191 | |
| 192 | // Test collective params build method no attributes, 2 inputs. |
| 193 | op = builder.create<test::TableGenBuildOp1>(loc, TypeRange{i32Ty}, |
| 194 | ValueRange{*cstI32, *cstF32}); |
| 195 | verifyOp(op, {i32Ty}, {*cstI32, *cstF32}, noAttrs); |
| 196 | |
| 197 | // Test collective params build method, non-empty attributes. |
| 198 | op = builder.create<test::TableGenBuildOp1>( |
| 199 | loc, TypeRange{i32Ty}, ValueRange{*cstI32, *cstF32}, attrs); |
| 200 | verifyOp(op, {i32Ty}, {*cstI32, *cstF32}, attrs); |
| 201 | } |
| 202 | |
| 203 | /// Test build methods for an Op with a single varadic arg and multiple variadic |
| 204 | /// result. |
| 205 | TEST_F(OpBuildGenTest, |
| 206 | BuildMethodsSingleVariadicArgAndMultipleVariadicResults) { |
| 207 | // Test separate arg, separate param build method. |
| 208 | auto op = builder.create<test::TableGenBuildOp3>( |
| 209 | loc, TypeRange{i32Ty}, TypeRange{f32Ty}, ValueRange{*cstI32}); |
| 210 | verifyOp(op, {i32Ty, f32Ty}, {*cstI32}, noAttrs); |
| 211 | |
| 212 | // Test collective params build method, no attributes. |
| 213 | op = builder.create<test::TableGenBuildOp3>(loc, TypeRange{i32Ty, f32Ty}, |
| 214 | ValueRange{*cstI32}); |
| 215 | verifyOp(op, {i32Ty, f32Ty}, {*cstI32}, noAttrs); |
| 216 | |
| 217 | // Test collective params build method, with attributes. |
| 218 | op = builder.create<test::TableGenBuildOp3>(loc, TypeRange{i32Ty, f32Ty}, |
| 219 | ValueRange{*cstI32}, attrs); |
| 220 | verifyOp(op, {i32Ty, f32Ty}, {*cstI32}, attrs); |
| 221 | } |
| 222 | |
| 223 | // The next test checks suppression of ambiguous build methods for ops that |
| 224 | // have a single variadic input, and single non-variadic result, and which |
| 225 | // support the SameOperandsAndResultType trait and optionally the |
| 226 | // InferOpTypeInterface interface. For such ops, the ODS framework generates |
| 227 | // build methods with no result types as they are inferred from the input types. |
| 228 | TEST_F(OpBuildGenTest, BuildMethodsSameOperandsAndResultTypeSuppression) { |
| 229 | // Test separate arg, separate param build method. |
| 230 | auto op = builder.create<test::TableGenBuildOp4>( |
| 231 | loc, i32Ty, ValueRange{*cstI32, *cstI32}); |
| 232 | verifyOp(std::move(op), {i32Ty}, {*cstI32, *cstI32}, noAttrs); |
| 233 | |
| 234 | // Test collective params build method. |
| 235 | op = builder.create<test::TableGenBuildOp4>(loc, TypeRange{i32Ty}, |
| 236 | ValueRange{*cstI32, *cstI32}); |
| 237 | verifyOp(std::move(op), {i32Ty}, {*cstI32, *cstI32}, noAttrs); |
| 238 | |
| 239 | // Test build method with no result types, default value of attributes. |
| 240 | op = |
| 241 | builder.create<test::TableGenBuildOp4>(loc, ValueRange{*cstI32, *cstI32}); |
| 242 | verifyOp(std::move(op), {i32Ty}, {*cstI32, *cstI32}, noAttrs); |
| 243 | |
| 244 | // Test build method with no result types and supplied attributes. |
| 245 | op = builder.create<test::TableGenBuildOp4>(loc, ValueRange{*cstI32, *cstI32}, |
| 246 | attrs); |
| 247 | verifyOp(std::move(op), {i32Ty}, {*cstI32, *cstI32}, attrs); |
| 248 | } |
| 249 | |
| 250 | TEST_F(OpBuildGenTest, BuildMethodsRegionsAndInferredType) { |
| 251 | auto op = builder.create<test::TableGenBuildOp5>( |
| 252 | loc, ValueRange{*cstI32, *cstF32}, /*attributes=*/noAttrs); |
| 253 | ASSERT_EQ(op->getNumRegions(), 1u); |
| 254 | verifyOp(op, {i32Ty}, {*cstI32, *cstF32}, noAttrs); |
| 255 | } |
| 256 | |
| 257 | TEST_F(OpBuildGenTest, BuildMethodsVariadicProperties) { |
| 258 | // Account for conversion as part of getAttrs(). |
| 259 | std::vector<NamedAttribute> ; |
| 260 | auto segmentSize = builder.getNamedAttr("operandSegmentSizes" , |
| 261 | builder.getDenseI32ArrayAttr({1, 1})); |
| 262 | noAttrsStorage.push_back(segmentSize); |
| 263 | ArrayRef<NamedAttribute> noAttrs(noAttrsStorage); |
| 264 | std::vector<NamedAttribute> = this->attrStorage; |
| 265 | attrsStorage.push_back(segmentSize); |
| 266 | ArrayRef<NamedAttribute> attrs(attrsStorage); |
| 267 | |
| 268 | // Test separate arg, separate param build method. |
| 269 | auto op = builder.create<test::TableGenBuildOp6>( |
| 270 | loc, f32Ty, ValueRange{*cstI32}, ValueRange{*cstI32}); |
| 271 | verifyOp(std::move(op), {f32Ty}, {*cstI32}, {*cstI32}, noAttrs); |
| 272 | |
| 273 | // Test build method with no result types, default value of attributes. |
| 274 | op = builder.create<test::TableGenBuildOp6>(loc, ValueRange{*cstI32}, |
| 275 | ValueRange{*cstI32}); |
| 276 | verifyOp(std::move(op), {f32Ty}, {*cstI32}, {*cstI32}, noAttrs); |
| 277 | |
| 278 | // Test collective params build method. |
| 279 | op = builder.create<test::TableGenBuildOp6>( |
| 280 | loc, TypeRange{f32Ty}, ValueRange{*cstI32}, ValueRange{*cstI32}); |
| 281 | verifyOp(std::move(op), {f32Ty}, {*cstI32}, {*cstI32}, noAttrs); |
| 282 | |
| 283 | // Test build method with result types, supplied attributes. |
| 284 | op = builder.create<test::TableGenBuildOp6>( |
| 285 | loc, TypeRange{f32Ty}, ValueRange{*cstI32, *cstI32}, attrs); |
| 286 | verifyOp(std::move(op), {f32Ty}, {*cstI32}, {*cstI32}, attrs); |
| 287 | |
| 288 | // Test build method with no result types and supplied attributes. |
| 289 | op = builder.create<test::TableGenBuildOp6>(loc, ValueRange{*cstI32, *cstI32}, |
| 290 | attrs); |
| 291 | verifyOp(std::move(op), {f32Ty}, {*cstI32}, {*cstI32}, attrs); |
| 292 | } |
| 293 | |
| 294 | TEST_F(OpBuildGenTest, BuildMethodsInherentDiscardableAttrs) { |
| 295 | test::TableGenBuildOp7::Properties props; |
| 296 | props.attr0 = cast<BoolAttr>(Val: attrs[0].getValue()); |
| 297 | ArrayRef<NamedAttribute> discardableAttrs = attrs.drop_front(); |
| 298 | auto op7 = builder.create<test::TableGenBuildOp7>( |
| 299 | loc, TypeRange{}, ValueRange{}, props, discardableAttrs); |
| 300 | verifyOp(op7, {}, {}, attrs); |
| 301 | |
| 302 | // Check that the old-style builder where all the attributes go in the same |
| 303 | // place works. |
| 304 | auto op7b = builder.create<test::TableGenBuildOp7>(loc, TypeRange{}, |
| 305 | ValueRange{}, attrs); |
| 306 | // Note: this goes before verifyOp() because verifyOp() calls erase(), causing |
| 307 | // use-after-free. |
| 308 | ASSERT_EQ(op7b.getProperties().getAttr0(), attrs[0].getValue()); |
| 309 | verifyOp(op7b, {}, {}, attrs); |
| 310 | } |
| 311 | |
| 312 | } // namespace mlir |
| 313 | |