blob: 87d922d22eaac23f20bc633afd81e4c302ab41ea [file] [log] [blame]
//===- 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<Module> M;
BasicBlock *BB = nullptr;
void SetUp() override {
M = std::make_unique<Module>("Test", Context);
auto F = M->getOrInsertFunction(
"test", FunctionType::get(Type::getVoidTy(Context), false));
BB = BasicBlock::Create(Context, "", cast<Function>(F.getCallee()));
EXPECT_NE(BB, nullptr);
}
void TearDown() override { M.reset(); }
public:
Instruction *makeIntrinsic(Intrinsic::ID ID) const {
IRBuilder<> Builder(BB);
SmallVector<Value *, 4> 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 <typename T> void checkIsa(const Instruction &I) {
EXPECT_TRUE(isa<T>(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<char[]>(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<StringRef, StringRef, ID> 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<StringRef, StringRef, ID> 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<InstrProfInstBase>(I);
};
#define __ISA(TYPE, PARENT) \
auto is##TYPE = [&](const Instruction &I) -> bool { \
return isa<TYPE>(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<Intrinsic::ID, std::function<bool(const Instruction &)>>>
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<Type *, 1> 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<Type *, 2> 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<Type *, 2> 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<Type *, 4> 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<Type *, 5> 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<Type *, 2> 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<Type *, 2> 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<Value *, 2> 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<Value *, 2> 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<Value *, 4> 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<Value *, 5> 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