|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | 
|  | // See https://llvm.org/LICENSE.txt for license information. | 
|  | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  | // | 
|  | // Unit tests for CIR implementation of OpenACC's PointertLikeType interface | 
|  | // | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | #include "mlir/Dialect/OpenACC/OpenACC.h" | 
|  | #include "mlir/IR/BuiltinTypes.h" | 
|  | #include "mlir/IR/Diagnostics.h" | 
|  | #include "mlir/IR/MLIRContext.h" | 
|  | #include "mlir/IR/Value.h" | 
|  | #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" | 
|  | #include "clang/CIR/Dialect/IR/CIRDialect.h" | 
|  | #include "clang/CIR/Dialect/IR/CIRTypes.h" | 
|  | #include "clang/CIR/Dialect/OpenACC/CIROpenACCTypeInterfaces.h" | 
|  | #include "clang/CIR/Dialect/OpenACC/RegisterOpenACCExtensions.h" | 
|  | #include "gtest/gtest.h" | 
|  |  | 
|  | using namespace mlir; | 
|  | using namespace cir; | 
|  |  | 
|  | //===----------------------------------------------------------------------===// | 
|  | // Test Fixture | 
|  | //===----------------------------------------------------------------------===// | 
|  |  | 
|  | class CIROpenACCPointerLikeTest : public ::testing::Test { | 
|  | protected: | 
|  | CIROpenACCPointerLikeTest() : b(&context), loc(UnknownLoc::get(&context)) { | 
|  | context.loadDialect<cir::CIRDialect>(); | 
|  | context.loadDialect<mlir::acc::OpenACCDialect>(); | 
|  |  | 
|  | // Register extension to integrate CIR types with OpenACC. | 
|  | mlir::DialectRegistry registry; | 
|  | cir::acc::registerOpenACCExtensions(registry); | 
|  | context.appendDialectRegistry(registry); | 
|  | } | 
|  |  | 
|  | MLIRContext context; | 
|  | OpBuilder b; | 
|  | Location loc; | 
|  | llvm::StringMap<unsigned> recordNames; | 
|  |  | 
|  | mlir::IntegerAttr getAlignOne(mlir::MLIRContext *ctx) { | 
|  | // Note that mlir::IntegerType is used instead of cir::IntType here because | 
|  | // we don't need sign information for this to be useful, so keep it simple. | 
|  | clang::CharUnits align = clang::CharUnits::One(); | 
|  | return b.getI64IntegerAttr(align.getQuantity()); | 
|  | } | 
|  |  | 
|  | mlir::StringAttr getUniqueRecordName(const std::string &baseName) { | 
|  | auto it = recordNames.find(baseName); | 
|  | if (it == recordNames.end()) { | 
|  | recordNames[baseName] = 0; | 
|  | return b.getStringAttr(baseName); | 
|  | } | 
|  |  | 
|  | return b.getStringAttr(baseName + "." + | 
|  | std::to_string(recordNames[baseName]++)); | 
|  | } | 
|  |  | 
|  | // General handler for types without a specific test | 
|  | void testSingleType(mlir::Type ty, | 
|  | mlir::acc::VariableTypeCategory expectedTypeCategory) { | 
|  | mlir::Type ptrTy = cir::PointerType::get(ty); | 
|  |  | 
|  | // cir::PointerType should be castable to acc::PointerLikeType | 
|  | auto pltTy = dyn_cast_if_present<mlir::acc::PointerLikeType>(ptrTy); | 
|  | ASSERT_NE(pltTy, nullptr); | 
|  |  | 
|  | EXPECT_EQ(pltTy.getElementType(), ty); | 
|  |  | 
|  | OwningOpRef<cir::AllocaOp> varPtrOp = | 
|  | b.create<cir::AllocaOp>(loc, ptrTy, ty, "", getAlignOne(&context)); | 
|  |  | 
|  | mlir::Value val = varPtrOp.get(); | 
|  | mlir::acc::VariableTypeCategory typeCategory = pltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(val), | 
|  | mlir::acc::getVarType(varPtrOp.get())); | 
|  |  | 
|  | EXPECT_EQ(typeCategory, expectedTypeCategory); | 
|  | } | 
|  |  | 
|  | void testScalarType(mlir::Type ty) { | 
|  | testSingleType(ty, mlir::acc::VariableTypeCategory::scalar); | 
|  | } | 
|  |  | 
|  | void testNonScalarType(mlir::Type ty) { | 
|  | testSingleType(ty, mlir::acc::VariableTypeCategory::nonscalar); | 
|  | } | 
|  |  | 
|  | void testUncategorizedType(mlir::Type ty) { | 
|  | testSingleType(ty, mlir::acc::VariableTypeCategory::uncategorized); | 
|  | } | 
|  |  | 
|  | void testArrayType(mlir::Type ty) { | 
|  | // Build the array pointer type. | 
|  | mlir::Type arrTy = cir::ArrayType::get(ty, 10); | 
|  | mlir::Type ptrTy = cir::PointerType::get(arrTy); | 
|  |  | 
|  | // Verify that the pointer points to the array type.. | 
|  | auto pltTy = dyn_cast_if_present<mlir::acc::PointerLikeType>(ptrTy); | 
|  | ASSERT_NE(pltTy, nullptr); | 
|  | EXPECT_EQ(pltTy.getElementType(), arrTy); | 
|  |  | 
|  | // Create an alloca for the array | 
|  | OwningOpRef<cir::AllocaOp> varPtrOp = | 
|  | b.create<cir::AllocaOp>(loc, ptrTy, arrTy, "", getAlignOne(&context)); | 
|  |  | 
|  | // Verify that the type category is array. | 
|  | mlir::Value val = varPtrOp.get(); | 
|  | mlir::acc::VariableTypeCategory typeCategory = pltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(val), | 
|  | mlir::acc::getVarType(varPtrOp.get())); | 
|  | EXPECT_EQ(typeCategory, mlir::acc::VariableTypeCategory::array); | 
|  |  | 
|  | // Create an array-to-pointer decay cast. | 
|  | mlir::Type ptrToElemTy = cir::PointerType::get(ty); | 
|  | OwningOpRef<cir::CastOp> decayPtr = b.create<cir::CastOp>( | 
|  | loc, ptrToElemTy, cir::CastKind::array_to_ptrdecay, val); | 
|  | mlir::Value decayVal = decayPtr.get(); | 
|  |  | 
|  | // Verify that we still get the expected element type. | 
|  | auto decayPltTy = | 
|  | dyn_cast_if_present<mlir::acc::PointerLikeType>(decayVal.getType()); | 
|  | ASSERT_NE(decayPltTy, nullptr); | 
|  | EXPECT_EQ(decayPltTy.getElementType(), ty); | 
|  |  | 
|  | // Verify that we still identify the type category as an array. | 
|  | mlir::acc::VariableTypeCategory decayTypeCategory = | 
|  | decayPltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(decayVal), | 
|  | mlir::acc::getVarType(decayPtr.get())); | 
|  | EXPECT_EQ(decayTypeCategory, mlir::acc::VariableTypeCategory::array); | 
|  |  | 
|  | // Create an element access. | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | mlir::Value index = | 
|  | b.create<cir::ConstantOp>(loc, cir::IntAttr::get(i32Ty, 2)); | 
|  | OwningOpRef<cir::PtrStrideOp> accessPtr = | 
|  | b.create<cir::PtrStrideOp>(loc, ptrToElemTy, decayVal, index); | 
|  | mlir::Value accessVal = accessPtr.get(); | 
|  |  | 
|  | // Verify that we still get the expected element type. | 
|  | auto accessPltTy = | 
|  | dyn_cast_if_present<mlir::acc::PointerLikeType>(accessVal.getType()); | 
|  | ASSERT_NE(accessPltTy, nullptr); | 
|  | EXPECT_EQ(accessPltTy.getElementType(), ty); | 
|  |  | 
|  | // Verify that we still identify the type category as an array. | 
|  | mlir::acc::VariableTypeCategory accessTypeCategory = | 
|  | accessPltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(accessVal), | 
|  | mlir::acc::getVarType(accessPtr.get())); | 
|  | EXPECT_EQ(accessTypeCategory, mlir::acc::VariableTypeCategory::array); | 
|  | } | 
|  |  | 
|  | // Structures and unions are accessed in the same way, so use a common test. | 
|  | void testRecordType(mlir::Type ty1, mlir::Type ty2, | 
|  | cir::RecordType::RecordKind kind) { | 
|  | // Build the structure pointer type. | 
|  | cir::RecordType structTy = | 
|  | cir::RecordType::get(&context, getUniqueRecordName("S"), kind); | 
|  | structTy.complete({ty1, ty2}, false, false); | 
|  | mlir::Type ptrTy = cir::PointerType::get(structTy); | 
|  |  | 
|  | // Verify that the pointer points to the structure type. | 
|  | auto pltTy = dyn_cast_if_present<mlir::acc::PointerLikeType>(ptrTy); | 
|  | ASSERT_NE(pltTy, nullptr); | 
|  | EXPECT_EQ(pltTy.getElementType(), structTy); | 
|  |  | 
|  | // Create an alloca for the array | 
|  | OwningOpRef<cir::AllocaOp> varPtrOp = b.create<cir::AllocaOp>( | 
|  | loc, ptrTy, structTy, "", getAlignOne(&context)); | 
|  |  | 
|  | // Verify that the type category is composite. | 
|  | mlir::Value val = varPtrOp.get(); | 
|  | mlir::acc::VariableTypeCategory typeCategory = pltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(val), | 
|  | mlir::acc::getVarType(varPtrOp.get())); | 
|  | EXPECT_EQ(typeCategory, mlir::acc::VariableTypeCategory::composite); | 
|  |  | 
|  | // Access the first element of the structure. | 
|  | OwningOpRef<cir::GetMemberOp> access1 = b.create<cir::GetMemberOp>( | 
|  | loc, cir::PointerType::get(ty1), val, b.getStringAttr("f1"), 0); | 
|  | mlir::Value accessVal1 = access1.get(); | 
|  |  | 
|  | // Verify that we get the expected element type. | 
|  | auto access1PltTy = | 
|  | dyn_cast_if_present<mlir::acc::PointerLikeType>(accessVal1.getType()); | 
|  | ASSERT_NE(access1PltTy, nullptr); | 
|  | EXPECT_EQ(access1PltTy.getElementType(), ty1); | 
|  |  | 
|  | // Verify that the type category is still composite. | 
|  | mlir::acc::VariableTypeCategory access1TypeCategory = | 
|  | access1PltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(accessVal1), | 
|  | mlir::acc::getVarType(access1.get())); | 
|  | EXPECT_EQ(access1TypeCategory, mlir::acc::VariableTypeCategory::composite); | 
|  |  | 
|  | // Access the second element of the structure. | 
|  | OwningOpRef<cir::GetMemberOp> access2 = b.create<cir::GetMemberOp>( | 
|  | loc, cir::PointerType::get(ty2), val, b.getStringAttr("f2"), 1); | 
|  | mlir::Value accessVal2 = access2.get(); | 
|  |  | 
|  | // Verify that we get the expected element type. | 
|  | auto access2PltTy = | 
|  | dyn_cast_if_present<mlir::acc::PointerLikeType>(accessVal2.getType()); | 
|  | ASSERT_NE(access2PltTy, nullptr); | 
|  | EXPECT_EQ(access2PltTy.getElementType(), ty2); | 
|  |  | 
|  | // Verify that the type category is still composite. | 
|  | mlir::acc::VariableTypeCategory access2TypeCategory = | 
|  | access2PltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(accessVal2), | 
|  | mlir::acc::getVarType(access2.get())); | 
|  | EXPECT_EQ(access2TypeCategory, mlir::acc::VariableTypeCategory::composite); | 
|  | } | 
|  |  | 
|  | void testStructType(mlir::Type ty1, mlir::Type ty2) { | 
|  | testRecordType(ty1, ty2, cir::RecordType::RecordKind::Struct); | 
|  | } | 
|  |  | 
|  | void testUnionType(mlir::Type ty1, mlir::Type ty2) { | 
|  | testRecordType(ty1, ty2, cir::RecordType::RecordKind::Union); | 
|  | } | 
|  |  | 
|  | // This is testing a case like this: | 
|  | // | 
|  | // struct S { | 
|  | //   int *f1; | 
|  | //   int *f2; | 
|  | // } *p; | 
|  | // int *pMember = p->f2; | 
|  | // | 
|  | // That is, we are not testing a pointer to a member, we're testing a pointer | 
|  | // that is loaded as a member value. | 
|  | void testPointerToMemberType( | 
|  | mlir::Type ty, mlir::acc::VariableTypeCategory expectedTypeCategory) { | 
|  | // Construct a struct type with two members that are pointers to the input | 
|  | // type. | 
|  | mlir::Type ptrTy = cir::PointerType::get(ty); | 
|  | cir::RecordType structTy = | 
|  | cir::RecordType::get(&context, getUniqueRecordName("S"), | 
|  | cir::RecordType::RecordKind::Struct); | 
|  | structTy.complete({ptrTy, ptrTy}, false, false); | 
|  | mlir::Type structPptrTy = cir::PointerType::get(structTy); | 
|  |  | 
|  | // Create an alloca for the struct. | 
|  | OwningOpRef<cir::AllocaOp> varPtrOp = b.create<cir::AllocaOp>( | 
|  | loc, structPptrTy, structTy, "S", getAlignOne(&context)); | 
|  | mlir::Value val = varPtrOp.get(); | 
|  |  | 
|  | // Get a pointer to the second member. | 
|  | OwningOpRef<cir::GetMemberOp> access = b.create<cir::GetMemberOp>( | 
|  | loc, cir::PointerType::get(ptrTy), val, b.getStringAttr("f2"), 1); | 
|  | mlir::Value accessVal = access.get(); | 
|  |  | 
|  | // Load the value of the second member. This is the pointer we want to test. | 
|  | OwningOpRef<cir::LoadOp> loadOp = b.create<cir::LoadOp>(loc, accessVal); | 
|  | mlir::Value loadVal = loadOp.get(); | 
|  |  | 
|  | // Verify that the type category is the expected type category. | 
|  | auto pltTy = dyn_cast_if_present<mlir::acc::PointerLikeType>(ptrTy); | 
|  | mlir::acc::VariableTypeCategory typeCategory = pltTy.getPointeeTypeCategory( | 
|  | cast<TypedValue<mlir::acc::PointerLikeType>>(loadVal), | 
|  | mlir::acc::getVarType(loadOp.get())); | 
|  |  | 
|  | EXPECT_EQ(typeCategory, expectedTypeCategory); | 
|  | } | 
|  | }; | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToInt) { | 
|  | // Test various scalar types. | 
|  | testScalarType(cir::IntType::get(&context, 8, true)); | 
|  | testScalarType(cir::IntType::get(&context, 8, false)); | 
|  | testScalarType(cir::IntType::get(&context, 16, true)); | 
|  | testScalarType(cir::IntType::get(&context, 16, false)); | 
|  | testScalarType(cir::IntType::get(&context, 32, true)); | 
|  | testScalarType(cir::IntType::get(&context, 32, false)); | 
|  | testScalarType(cir::IntType::get(&context, 64, true)); | 
|  | testScalarType(cir::IntType::get(&context, 64, false)); | 
|  | testScalarType(cir::IntType::get(&context, 128, true)); | 
|  | testScalarType(cir::IntType::get(&context, 128, false)); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToBool) { | 
|  | testScalarType(cir::BoolType::get(&context)); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToFloat) { | 
|  | testScalarType(cir::SingleType::get(&context)); | 
|  | testScalarType(cir::DoubleType::get(&context)); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToPointer) { | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | mlir::Type ptrTy = cir::PointerType::get(i32Ty); | 
|  | testScalarType(ptrTy); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToArray) { | 
|  | // Test an array type. | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | testArrayType(i32Ty); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToStruct) { | 
|  | // Test a struct type. | 
|  | mlir::Type i16Ty = cir::IntType::get(&context, 16, true); | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | testStructType(i16Ty, i32Ty); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToUnion) { | 
|  | // Test a union type. | 
|  | mlir::Type i16Ty = cir::IntType::get(&context, 16, true); | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | testUnionType(i16Ty, i32Ty); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToFunction) { | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | mlir::Type funcTy = | 
|  | cir::FuncType::get(SmallVector<mlir::Type, 2>{i32Ty, i32Ty}, i32Ty); | 
|  | testNonScalarType(funcTy); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToVector) { | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | mlir::Type vecTy = cir::VectorType::get(i32Ty, 4); | 
|  | testNonScalarType(vecTy); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToVoid) { | 
|  | mlir::Type voidTy = cir::VoidType::get(&context); | 
|  | testUncategorizedType(voidTy); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToIntMember) { | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | testPointerToMemberType(i32Ty, mlir::acc::VariableTypeCategory::scalar); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToArrayMember) { | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | mlir::Type arrTy = cir::ArrayType::get(i32Ty, 10); | 
|  | testPointerToMemberType(arrTy, mlir::acc::VariableTypeCategory::array); | 
|  | } | 
|  |  | 
|  | TEST_F(CIROpenACCPointerLikeTest, testPointerToStructMember) { | 
|  | mlir::Type i32Ty = cir::IntType::get(&context, 32, true); | 
|  | cir::RecordType structTy = cir::RecordType::get( | 
|  | &context, getUniqueRecordName("S"), cir::RecordType::RecordKind::Struct); | 
|  | structTy.complete({i32Ty, i32Ty}, false, false); | 
|  | testPointerToMemberType(structTy, mlir::acc::VariableTypeCategory::composite); | 
|  | } |