//===- llvm/unittest/IR/IntrinsicsTest.cpp - ------------------------------===// // // 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 // //===----------------------------------------------------------------------===// #include "llvm/IR/Intrinsics.h" #include "llvm/ADT/SmallVector.h" #include "llvm/IR/Constant.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/IntrinsicsAArch64.h" #include "llvm/IR/IntrinsicsAMDGPU.h" #include "llvm/IR/IntrinsicsARM.h" #include "llvm/IR/IntrinsicsBPF.h" #include "llvm/IR/IntrinsicsDirectX.h" #include "llvm/IR/IntrinsicsHexagon.h" #include "llvm/IR/IntrinsicsLoongArch.h" #include "llvm/IR/IntrinsicsMips.h" #include "llvm/IR/IntrinsicsNVPTX.h" #include "llvm/IR/IntrinsicsPowerPC.h" #include "llvm/IR/IntrinsicsRISCV.h" #include "llvm/IR/IntrinsicsS390.h" #include "llvm/IR/IntrinsicsX86.h" #include "llvm/IR/Module.h" #include "gtest/gtest.h" using namespace llvm; namespace { class IntrinsicsTest : public ::testing::Test { protected: LLVMContext Context; std::unique_ptr M; BasicBlock *BB = nullptr; void SetUp() override { M = std::make_unique("Test", Context); auto F = M->getOrInsertFunction( "test", FunctionType::get(Type::getVoidTy(Context), false)); BB = BasicBlock::Create(Context, "", cast(F.getCallee())); EXPECT_NE(BB, nullptr); } void TearDown() override { M.reset(); } public: Instruction *makeIntrinsic(Intrinsic::ID ID) const { IRBuilder<> Builder(BB); SmallVector ProcessedArgs; auto *Decl = Intrinsic::getOrInsertDeclaration(M.get(), ID); for (auto *Ty : Decl->getFunctionType()->params()) { auto *Val = Constant::getNullValue(Ty); ProcessedArgs.push_back(Val); } return Builder.CreateCall(Decl, ProcessedArgs); } template void checkIsa(const Instruction &I) { EXPECT_TRUE(isa(I)); } }; TEST(IntrinsicNameLookup, Basic) { using namespace Intrinsic; EXPECT_EQ(Intrinsic::memcpy, lookupIntrinsicID("llvm.memcpy")); // Partial, either between dots or not the last dot are not intrinsics. EXPECT_EQ(not_intrinsic, lookupIntrinsicID("llvm.mem")); EXPECT_EQ(not_intrinsic, lookupIntrinsicID("llvm.gc")); // Look through intrinsic names with internal dots. EXPECT_EQ(memcpy_inline, lookupIntrinsicID("llvm.memcpy.inline")); // Check that overloaded names are mapped to the underlying ID. EXPECT_EQ(memcpy_inline, lookupIntrinsicID("llvm.memcpy.inline.p0.p0.i8")); EXPECT_EQ(memcpy_inline, lookupIntrinsicID("llvm.memcpy.inline.p0.p0.i32")); EXPECT_EQ(memcpy_inline, lookupIntrinsicID("llvm.memcpy.inline.p0.p0.i64")); EXPECT_EQ(memcpy_inline, lookupIntrinsicID("llvm.memcpy.inline.p0.p0.i1024")); } TEST(IntrinsicNameLookup, NonNullterminatedStringRef) { using namespace Intrinsic; // This reproduces an issue where lookupIntrinsicID() can access memory beyond // the bounds of the passed in StringRef. For ASAN to catch this as an error, // create a StringRef using heap allocated memory and make it not null // terminated. // ASAN will report a "AddressSanitizer: heap-buffer-overflow" error in // `lookupLLVMIntrinsicByName` when LLVM is built with these options: // -DCMAKE_BUILD_TYPE=Debug // -DLLVM_USE_SANITIZER=Address // -DLLVM_OPTIMIZE_SANITIZED_BUILDS=OFF // Make an intrinsic name "llvm.memcpy.inline" on the heap. std::string Name = "llvm.memcpy.inline"; assert(Name.size() == 18); // Create a StringRef backed by heap allocated memory such that OOB access // in that StringRef can be flagged by asan. Here, the String `S` is of size // 18, and backed by a heap allocated buffer `Data`, so access to S[18] will // be flagged bby asan. auto Data = std::make_unique(Name.size()); std::strncpy(Data.get(), Name.data(), Name.size()); StringRef S(Data.get(), Name.size()); EXPECT_EQ(memcpy_inline, lookupIntrinsicID(S)); } // Tests to verify getIntrinsicForClangBuiltin. TEST(IntrinsicNameLookup, ClangBuiltinLookup) { using namespace Intrinsic; static constexpr std::tuple ClangTests[] = { {"__builtin_adjust_trampoline", "", adjust_trampoline}, {"__builtin_trap", "", trap}, {"__builtin_arm_chkfeat", "aarch64", aarch64_chkfeat}, {"__builtin_amdgcn_alignbyte", "amdgcn", amdgcn_alignbyte}, {"__builtin_amdgcn_workgroup_id_z", "amdgcn", amdgcn_workgroup_id_z}, {"__builtin_arm_cdp", "arm", arm_cdp}, {"__builtin_bpf_preserve_type_info", "bpf", bpf_preserve_type_info}, {"__builtin_HEXAGON_A2_tfr", "hexagon", hexagon_A2_tfr}, {"__builtin_lasx_xbz_w", "loongarch", loongarch_lasx_xbz_w}, {"__builtin_mips_bitrev", "mips", mips_bitrev}, {"__nvvm_add_rn_d", "nvvm", nvvm_add_rn_d}, {"__builtin_altivec_dss", "ppc", ppc_altivec_dss}, {"__builtin_riscv_sha512sum1r", "riscv", riscv_sha512sum1r}, {"__builtin_tend", "s390", s390_tend}, {"__builtin_ia32_pause", "x86", x86_sse2_pause}, {"__does_not_exist", "", not_intrinsic}, {"__does_not_exist", "arm", not_intrinsic}, {"__builtin_arm_cdp", "", not_intrinsic}, {"__builtin_arm_cdp", "x86", not_intrinsic}, }; for (const auto &[Builtin, Target, ID] : ClangTests) EXPECT_EQ(ID, getIntrinsicForClangBuiltin(Target, Builtin)); } // Tests to verify getIntrinsicForMSBuiltin. TEST(IntrinsicNameLookup, MSBuiltinLookup) { using namespace Intrinsic; static constexpr std::tuple MSTests[] = { {"__dmb", "aarch64", aarch64_dmb}, {"__dmb", "arm", arm_dmb}, {"__dmb", "", not_intrinsic}, {"__does_not_exist", "", not_intrinsic}, {"__does_not_exist", "arm", not_intrinsic}, }; for (const auto &[Builtin, Target, ID] : MSTests) EXPECT_EQ(ID, getIntrinsicForMSBuiltin(Target, Builtin)); } TEST_F(IntrinsicsTest, InstrProfInheritance) { auto isInstrProfInstBase = [](const Instruction &I) { return isa(I); }; #define __ISA(TYPE, PARENT) \ auto is##TYPE = [&](const Instruction &I) -> bool { \ return isa(I) && is##PARENT(I); \ } __ISA(InstrProfCntrInstBase, InstrProfInstBase); __ISA(InstrProfCoverInst, InstrProfCntrInstBase); __ISA(InstrProfIncrementInst, InstrProfCntrInstBase); __ISA(InstrProfIncrementInstStep, InstrProfIncrementInst); __ISA(InstrProfCallsite, InstrProfCntrInstBase); __ISA(InstrProfTimestampInst, InstrProfCntrInstBase); __ISA(InstrProfValueProfileInst, InstrProfCntrInstBase); __ISA(InstrProfMCDCBitmapInstBase, InstrProfInstBase); __ISA(InstrProfMCDCBitmapParameters, InstrProfMCDCBitmapInstBase); __ISA(InstrProfMCDCTVBitmapUpdate, InstrProfMCDCBitmapInstBase); #undef __ISA std::vector< std::pair>> LeafIDs = { {Intrinsic::instrprof_cover, isInstrProfCoverInst}, {Intrinsic::instrprof_increment, isInstrProfIncrementInst}, {Intrinsic::instrprof_increment_step, isInstrProfIncrementInstStep}, {Intrinsic::instrprof_callsite, isInstrProfCallsite}, {Intrinsic::instrprof_mcdc_parameters, isInstrProfMCDCBitmapParameters}, {Intrinsic::instrprof_mcdc_tvbitmap_update, isInstrProfMCDCTVBitmapUpdate}, {Intrinsic::instrprof_timestamp, isInstrProfTimestampInst}, {Intrinsic::instrprof_value_profile, isInstrProfValueProfileInst}}; for (const auto &[ID, Checker] : LeafIDs) { auto *Intr = makeIntrinsic(ID); EXPECT_TRUE(Checker(*Intr)); } } // Check that getFnAttributes for intrinsics that do not have any function // attributes correcty returns an empty set. TEST(IntrinsicAttributes, TestGetFnAttributesBug) { using namespace Intrinsic; LLVMContext Context; AttributeSet AS = getFnAttributes(Context, experimental_guard); EXPECT_FALSE(AS.hasAttributes()); } // Tests non-overloaded intrinsic declaration. TEST_F(IntrinsicsTest, NonOverloadedIntrinsic) { Type *RetTy = Type::getVoidTy(Context); SmallVector ArgTys; ArgTys.push_back(Type::getInt1Ty(Context)); Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::assume, RetTy, ArgTys); ASSERT_NE(F, nullptr); EXPECT_EQ(F->getIntrinsicID(), Intrinsic::assume); EXPECT_EQ(F->getReturnType(), RetTy); EXPECT_EQ(F->arg_size(), 1u); EXPECT_FALSE(F->isVarArg()); EXPECT_EQ(F->getName(), "llvm.assume"); } // Tests overloaded intrinsic with automatic type resolution for scalar types. TEST_F(IntrinsicsTest, OverloadedIntrinsicScalar) { Type *RetTy = Type::getInt32Ty(Context); SmallVector ArgTys; ArgTys.push_back(Type::getInt32Ty(Context)); ArgTys.push_back(Type::getInt32Ty(Context)); Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, RetTy, ArgTys); ASSERT_NE(F, nullptr); EXPECT_EQ(F->getIntrinsicID(), Intrinsic::umax); EXPECT_EQ(F->getReturnType(), RetTy); EXPECT_EQ(F->arg_size(), 2u); EXPECT_FALSE(F->isVarArg()); EXPECT_EQ(F->getName(), "llvm.umax.i32"); } // Tests overloaded intrinsic with automatic type resolution for vector types. TEST_F(IntrinsicsTest, OverloadedIntrinsicVector) { Type *RetTy = FixedVectorType::get(Type::getInt32Ty(Context), 4); SmallVector ArgTys; ArgTys.push_back(RetTy); ArgTys.push_back(RetTy); Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, RetTy, ArgTys); ASSERT_NE(F, nullptr); EXPECT_EQ(F->getIntrinsicID(), Intrinsic::umax); EXPECT_EQ(F->getReturnType(), RetTy); EXPECT_EQ(F->arg_size(), 2u); EXPECT_FALSE(F->isVarArg()); EXPECT_EQ(F->getName(), "llvm.umax.v4i32"); } // Tests overloaded intrinsic with automatic type resolution for addrspace. TEST_F(IntrinsicsTest, OverloadedIntrinsicAddressSpace) { Type *RetTy = Type::getVoidTy(Context); SmallVector ArgTys; ArgTys.push_back(PointerType::get(Context, 1)); // ptr addrspace(1) ArgTys.push_back(Type::getInt32Ty(Context)); // rw ArgTys.push_back(Type::getInt32Ty(Context)); // locality ArgTys.push_back(Type::getInt32Ty(Context)); // cache type Function *F = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::prefetch, RetTy, ArgTys); ASSERT_NE(F, nullptr); EXPECT_EQ(F->getIntrinsicID(), Intrinsic::prefetch); EXPECT_EQ(F->getReturnType(), RetTy); EXPECT_EQ(F->arg_size(), 4u); EXPECT_FALSE(F->isVarArg()); EXPECT_EQ(F->getName(), "llvm.prefetch.p1"); } // Tests vararg intrinsic declaration. TEST_F(IntrinsicsTest, VarArgIntrinsicStatepoint) { Type *RetTy = Type::getTokenTy(Context); SmallVector ArgTys; ArgTys.push_back(Type::getInt64Ty(Context)); // ID ArgTys.push_back(Type::getInt32Ty(Context)); // NumPatchBytes ArgTys.push_back(PointerType::get(Context, 0)); // Target ArgTys.push_back(Type::getInt32Ty(Context)); // NumCallArgs ArgTys.push_back(Type::getInt32Ty(Context)); // Flags Function *F = Intrinsic::getOrInsertDeclaration( M.get(), Intrinsic::experimental_gc_statepoint, RetTy, ArgTys); ASSERT_NE(F, nullptr); EXPECT_EQ(F->getIntrinsicID(), Intrinsic::experimental_gc_statepoint); EXPECT_EQ(F->getReturnType(), RetTy); EXPECT_EQ(F->arg_size(), 5u); EXPECT_TRUE(F->isVarArg()) << "experimental_gc_statepoint must be vararg"; EXPECT_EQ(F->getName(), "llvm.experimental.gc.statepoint.p0"); } // Tests that different overloads create different declarations. TEST_F(IntrinsicsTest, DifferentOverloads) { // i32 version Type *RetTy32 = Type::getInt32Ty(Context); SmallVector ArgTys32; ArgTys32.push_back(Type::getInt32Ty(Context)); ArgTys32.push_back(Type::getInt32Ty(Context)); Function *Func32 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, RetTy32, ArgTys32); // i64 version Type *RetTy64 = Type::getInt64Ty(Context); SmallVector ArgTys64; ArgTys64.push_back(Type::getInt64Ty(Context)); ArgTys64.push_back(Type::getInt64Ty(Context)); Function *Func64 = Intrinsic::getOrInsertDeclaration(M.get(), Intrinsic::umax, RetTy64, ArgTys64); EXPECT_NE(Func32, Func64) << "Different overloads should be different functions"; EXPECT_EQ(Func32->getName(), "llvm.umax.i32"); EXPECT_EQ(Func64->getName(), "llvm.umax.i64"); } // Tests IRBuilder::CreateIntrinsic with overloaded scalar type. TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicScalar) { IRBuilder<> Builder(BB); Type *RetTy = Type::getInt32Ty(Context); SmallVector Args; Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 10)); Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 20)); CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::umax, Args); ASSERT_NE(CI, nullptr); EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::umax); EXPECT_EQ(CI->getType(), RetTy); EXPECT_EQ(CI->arg_size(), 2u); EXPECT_FALSE(CI->getCalledFunction()->isVarArg()); } // Tests IRBuilder::CreateIntrinsic with overloaded vector type. TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicVector) { IRBuilder<> Builder(BB); Type *RetTy = FixedVectorType::get(Type::getInt32Ty(Context), 4); SmallVector Args; Args.push_back(Constant::getNullValue(RetTy)); Args.push_back(Constant::getNullValue(RetTy)); CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::umax, Args); ASSERT_NE(CI, nullptr); EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::umax); EXPECT_EQ(CI->getType(), RetTy); EXPECT_EQ(CI->arg_size(), 2u); EXPECT_FALSE(CI->getCalledFunction()->isVarArg()); } // Tests IRBuilder::CreateIntrinsic with overloaded address space. TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicAddressSpace) { IRBuilder<> Builder(BB); Type *RetTy = Type::getVoidTy(Context); SmallVector Args; Args.push_back(Constant::getNullValue( PointerType::get(Context, 1))); // ptr addrspace(1) null Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // rw Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 3)); // locality Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 1)); // cache type CallInst *CI = Builder.CreateIntrinsic(RetTy, Intrinsic::prefetch, Args); ASSERT_NE(CI, nullptr); EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::prefetch); EXPECT_EQ(CI->getType(), RetTy); EXPECT_EQ(CI->arg_size(), 4u); EXPECT_FALSE(CI->getCalledFunction()->isVarArg()); EXPECT_EQ(CI->getCalledFunction()->getName(), "llvm.prefetch.p1"); } // Tests IRBuilder::CreateIntrinsic with vararg intrinsic. TEST_F(IntrinsicsTest, IRBuilderCreateIntrinsicVarArg) { IRBuilder<> Builder(BB); // Create a dummy function to call through statepoint FunctionType *DummyFnTy = FunctionType::get(Type::getVoidTy(Context), false); Function *DummyFn = Function::Create(DummyFnTy, GlobalValue::ExternalLinkage, "dummy", M.get()); Type *RetTy = Type::getTokenTy(Context); SmallVector Args; Args.push_back(ConstantInt::get(Type::getInt64Ty(Context), 0)); // ID Args.push_back( ConstantInt::get(Type::getInt32Ty(Context), 0)); // NumPatchBytes Args.push_back(DummyFn); // Target Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // NumCallArgs Args.push_back(ConstantInt::get(Type::getInt32Ty(Context), 0)); // Flags CallInst *CI = Builder.CreateIntrinsic( RetTy, Intrinsic::experimental_gc_statepoint, Args); ASSERT_NE(CI, nullptr); EXPECT_EQ(CI->getIntrinsicID(), Intrinsic::experimental_gc_statepoint); EXPECT_EQ(CI->getType(), RetTy); EXPECT_EQ(CI->arg_size(), 5u); EXPECT_TRUE(CI->getCalledFunction()->isVarArg()) << "experimental_gc_statepoint must be vararg"; } } // end namespace