[CIR] Upstream minimal builtin function call support (#142981)
This patch adds all bits required to implement builtin function calls to ClangIR. It doesn't actually implement any of the builtins except those that fold to a constant ahead of CodeGen (`__builtin_is_constant_evaluated()` being one example).
This commit is contained in:
parent
78765bb856
commit
8e4f0d8614
@ -83,7 +83,6 @@ struct MissingFeatures {
|
||||
static bool opFuncSetComdat() { return false; }
|
||||
|
||||
// CallOp handling
|
||||
static bool opCallBuiltinFunc() { return false; }
|
||||
static bool opCallPseudoDtor() { return false; }
|
||||
static bool opCallAggregateArgs() { return false; }
|
||||
static bool opCallPaddingArgs() { return false; }
|
||||
@ -225,6 +224,8 @@ struct MissingFeatures {
|
||||
static bool isMemcpyEquivalentSpecialMember() { return false; }
|
||||
static bool isTrivialCtorOrDtor() { return false; }
|
||||
static bool implicitConstructorArgs() { return false; }
|
||||
static bool intrinsics() { return false; }
|
||||
static bool attributeNoBuiltin() { return false; }
|
||||
|
||||
// Missing types
|
||||
static bool dataMemberType() { return false; }
|
||||
|
||||
@ -39,6 +39,34 @@ mlir::Value CIRGenBuilderTy::getArrayElement(mlir::Location arrayLocBegin,
|
||||
return create<cir::PtrStrideOp>(arrayLocEnd, flatPtrTy, basePtr, idx);
|
||||
}
|
||||
|
||||
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
|
||||
llvm::APSInt intVal) {
|
||||
bool isSigned = intVal.isSigned();
|
||||
unsigned width = intVal.getBitWidth();
|
||||
cir::IntType t = isSigned ? getSIntNTy(width) : getUIntNTy(width);
|
||||
return getConstInt(loc, t,
|
||||
isSigned ? intVal.getSExtValue() : intVal.getZExtValue());
|
||||
}
|
||||
|
||||
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
|
||||
llvm::APInt intVal) {
|
||||
return getConstInt(loc, llvm::APSInt(intVal));
|
||||
}
|
||||
|
||||
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc, mlir::Type t,
|
||||
uint64_t c) {
|
||||
assert(mlir::isa<cir::IntType>(t) && "expected cir::IntType");
|
||||
return create<cir::ConstantOp>(loc, cir::IntAttr::get(t, c));
|
||||
}
|
||||
|
||||
cir::ConstantOp
|
||||
clang::CIRGen::CIRGenBuilderTy::getConstFP(mlir::Location loc, mlir::Type t,
|
||||
llvm::APFloat fpVal) {
|
||||
assert(mlir::isa<cir::CIRFPTypeInterface>(t) &&
|
||||
"expected floating point type");
|
||||
return create<cir::ConstantOp>(loc, getAttr<cir::FPAttr>(t, fpVal));
|
||||
}
|
||||
|
||||
// This can't be defined in Address.h because that file is included by
|
||||
// CIRGenBuilder.h
|
||||
Address Address::withElementType(CIRGenBuilderTy &builder,
|
||||
|
||||
@ -11,10 +11,12 @@
|
||||
|
||||
#include "Address.h"
|
||||
#include "CIRGenTypeCache.h"
|
||||
#include "clang/CIR/Interfaces/CIRFPTypeInterface.h"
|
||||
#include "clang/CIR/MissingFeatures.h"
|
||||
|
||||
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
|
||||
#include "clang/CIR/MissingFeatures.h"
|
||||
#include "llvm/ADT/APFloat.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
|
||||
namespace clang::CIRGen {
|
||||
@ -229,6 +231,15 @@ public:
|
||||
cir::IntType getUInt32Ty() { return typeCache.UInt32Ty; }
|
||||
cir::IntType getUInt64Ty() { return typeCache.UInt64Ty; }
|
||||
|
||||
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APSInt intVal);
|
||||
|
||||
cir::ConstantOp getConstInt(mlir::Location loc, llvm::APInt intVal);
|
||||
|
||||
cir::ConstantOp getConstInt(mlir::Location loc, mlir::Type t, uint64_t c);
|
||||
|
||||
cir::ConstantOp getConstFP(mlir::Location loc, mlir::Type t,
|
||||
llvm::APFloat fpVal);
|
||||
|
||||
bool isInt8Ty(mlir::Type i) {
|
||||
return i == typeCache.UInt8Ty || i == typeCache.SInt8Ty;
|
||||
}
|
||||
|
||||
55
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Normal file
55
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// This contains code to emit Builtin calls as CIR or a function call to be
|
||||
// later resolved.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "CIRGenCall.h"
|
||||
#include "CIRGenFunction.h"
|
||||
#include "CIRGenModule.h"
|
||||
#include "CIRGenValue.h"
|
||||
#include "mlir/IR/BuiltinAttributes.h"
|
||||
#include "mlir/IR/Value.h"
|
||||
#include "mlir/Support/LLVM.h"
|
||||
#include "clang/AST/Expr.h"
|
||||
#include "clang/AST/GlobalDecl.h"
|
||||
#include "llvm/Support/ErrorHandling.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace clang::CIRGen;
|
||||
|
||||
RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
|
||||
const CallExpr *e,
|
||||
ReturnValueSlot returnValue) {
|
||||
// See if we can constant fold this builtin. If so, don't emit it at all.
|
||||
// TODO: Extend this handling to all builtin calls that we can constant-fold.
|
||||
Expr::EvalResult result;
|
||||
if (e->isPRValue() && e->EvaluateAsRValue(result, cgm.getASTContext()) &&
|
||||
!result.hasSideEffects()) {
|
||||
if (result.Val.isInt()) {
|
||||
return RValue::get(builder.getConstInt(getLoc(e->getSourceRange()),
|
||||
result.Val.getInt()));
|
||||
}
|
||||
if (result.Val.isFloat()) {
|
||||
// Note: we are using result type of CallExpr to determine the type of
|
||||
// the constant. Classic codegen uses the result value to determine the
|
||||
// type. We feel it should be Ok to use expression type because it is
|
||||
// hard to imagine a builtin function evaluates to a value that
|
||||
// over/underflows its own defined type.
|
||||
mlir::Type type = convertType(e->getType());
|
||||
return RValue::get(builder.getConstFP(getLoc(e->getExprLoc()), type,
|
||||
result.Val.getFloat()));
|
||||
}
|
||||
}
|
||||
|
||||
mlir::Location loc = getLoc(e->getExprLoc());
|
||||
cgm.errorNYI(loc, "non constant foldable builtin calls");
|
||||
return getUndefRValue(e->getType());
|
||||
}
|
||||
@ -44,16 +44,25 @@ public:
|
||||
class CIRGenCallee {
|
||||
enum class SpecialKind : uintptr_t {
|
||||
Invalid,
|
||||
Builtin,
|
||||
|
||||
Last = Invalid,
|
||||
Last = Builtin,
|
||||
};
|
||||
|
||||
struct BuiltinInfoStorage {
|
||||
const clang::FunctionDecl *decl;
|
||||
unsigned id;
|
||||
};
|
||||
|
||||
SpecialKind kindOrFunctionPtr;
|
||||
|
||||
union {
|
||||
CIRGenCalleeInfo abstractInfo;
|
||||
BuiltinInfoStorage builtinInfo;
|
||||
};
|
||||
|
||||
explicit CIRGenCallee(SpecialKind kind) : kindOrFunctionPtr(kind) {}
|
||||
|
||||
public:
|
||||
CIRGenCallee() : kindOrFunctionPtr(SpecialKind::Invalid) {}
|
||||
|
||||
@ -69,6 +78,25 @@ public:
|
||||
return CIRGenCallee(abstractInfo, funcPtr);
|
||||
}
|
||||
|
||||
bool isBuiltin() const { return kindOrFunctionPtr == SpecialKind::Builtin; }
|
||||
|
||||
const clang::FunctionDecl *getBuiltinDecl() const {
|
||||
assert(isBuiltin());
|
||||
return builtinInfo.decl;
|
||||
}
|
||||
unsigned getBuiltinID() const {
|
||||
assert(isBuiltin());
|
||||
return builtinInfo.id;
|
||||
}
|
||||
|
||||
static CIRGenCallee forBuiltin(unsigned builtinID,
|
||||
const clang::FunctionDecl *builtinDecl) {
|
||||
CIRGenCallee result(SpecialKind::Builtin);
|
||||
result.builtinInfo.decl = builtinDecl;
|
||||
result.builtinInfo.id = builtinID;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool isOrdinary() const {
|
||||
return uintptr_t(kindOrFunctionPtr) > uintptr_t(SpecialKind::Last);
|
||||
}
|
||||
|
||||
@ -1029,8 +1029,48 @@ static cir::FuncOp emitFunctionDeclPointer(CIRGenModule &cgm, GlobalDecl gd) {
|
||||
return cgm.getAddrOfFunction(gd);
|
||||
}
|
||||
|
||||
static CIRGenCallee emitDirectCallee(CIRGenModule &cgm, GlobalDecl gd) {
|
||||
assert(!cir::MissingFeatures::opCallBuiltinFunc());
|
||||
// Detect the unusual situation where an inline version is shadowed by a
|
||||
// non-inline version. In that case we should pick the external one
|
||||
// everywhere. That's GCC behavior too.
|
||||
static bool onlyHasInlineBuiltinDeclaration(const FunctionDecl *fd) {
|
||||
for (const FunctionDecl *pd = fd; pd; pd = pd->getPreviousDecl())
|
||||
if (!pd->isInlineBuiltinDeclaration())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
CIRGenCallee CIRGenFunction::emitDirectCallee(const GlobalDecl &gd) {
|
||||
const auto *fd = cast<FunctionDecl>(gd.getDecl());
|
||||
|
||||
if (unsigned builtinID = fd->getBuiltinID()) {
|
||||
if (fd->getAttr<AsmLabelAttr>()) {
|
||||
cgm.errorNYI("AsmLabelAttr");
|
||||
}
|
||||
|
||||
StringRef ident = fd->getName();
|
||||
std::string fdInlineName = (ident + ".inline").str();
|
||||
|
||||
bool isPredefinedLibFunction =
|
||||
cgm.getASTContext().BuiltinInfo.isPredefinedLibFunction(builtinID);
|
||||
bool hasAttributeNoBuiltin = false;
|
||||
assert(!cir::MissingFeatures::attributeNoBuiltin());
|
||||
|
||||
// When directing calling an inline builtin, call it through it's mangled
|
||||
// name to make it clear it's not the actual builtin.
|
||||
auto fn = cast<cir::FuncOp>(curFn);
|
||||
if (fn.getName() != fdInlineName && onlyHasInlineBuiltinDeclaration(fd)) {
|
||||
cgm.errorNYI("Inline only builtin function calls");
|
||||
}
|
||||
|
||||
// Replaceable builtins provide their own implementation of a builtin. If we
|
||||
// are in an inline builtin implementation, avoid trivial infinite
|
||||
// recursion. Honor __attribute__((no_builtin("foo"))) or
|
||||
// __attribute__((no_builtin)) on the current function unless foo is
|
||||
// not a predefined library function which means we must generate the
|
||||
// builtin no matter what.
|
||||
else if (!isPredefinedLibFunction || !hasAttributeNoBuiltin)
|
||||
return CIRGenCallee::forBuiltin(builtinID, fd);
|
||||
}
|
||||
|
||||
cir::FuncOp callee = emitFunctionDeclPointer(cgm, gd);
|
||||
|
||||
@ -1106,7 +1146,7 @@ CIRGenCallee CIRGenFunction::emitCallee(const clang::Expr *e) {
|
||||
} else if (const auto *declRef = dyn_cast<DeclRefExpr>(e)) {
|
||||
// Resolve direct calls.
|
||||
const auto *funcDecl = cast<FunctionDecl>(declRef->getDecl());
|
||||
return emitDirectCallee(cgm, funcDecl);
|
||||
return emitDirectCallee(funcDecl);
|
||||
} else if (isa<MemberExpr>(e)) {
|
||||
cgm.errorNYI(e->getSourceRange(),
|
||||
"emitCallee: call to member function is NYI");
|
||||
@ -1162,10 +1202,9 @@ RValue CIRGenFunction::emitCallExpr(const clang::CallExpr *e,
|
||||
|
||||
CIRGenCallee callee = emitCallee(e->getCallee());
|
||||
|
||||
if (e->getBuiltinCallee()) {
|
||||
cgm.errorNYI(e->getSourceRange(), "call to builtin functions");
|
||||
}
|
||||
assert(!cir::MissingFeatures::opCallBuiltinFunc());
|
||||
if (callee.isBuiltin())
|
||||
return emitBuiltinExpr(callee.getBuiltinDecl(), callee.getBuiltinID(), e,
|
||||
returnValue);
|
||||
|
||||
if (isa<CXXPseudoDestructorExpr>(e->getCallee())) {
|
||||
cgm.errorNYI(e->getSourceRange(), "call to pseudo destructor");
|
||||
|
||||
@ -665,6 +665,8 @@ private:
|
||||
void emitAndUpdateRetAlloca(clang::QualType type, mlir::Location loc,
|
||||
clang::CharUnits alignment);
|
||||
|
||||
CIRGenCallee emitDirectCallee(const GlobalDecl &gd);
|
||||
|
||||
public:
|
||||
Address emitAddrOfFieldStorage(Address base, const FieldDecl *field,
|
||||
llvm::StringRef fieldName,
|
||||
@ -711,6 +713,9 @@ public:
|
||||
|
||||
mlir::LogicalResult emitBreakStmt(const clang::BreakStmt &s);
|
||||
|
||||
RValue emitBuiltinExpr(const clang::GlobalDecl &gd, unsigned builtinID,
|
||||
const clang::CallExpr *e, ReturnValueSlot returnValue);
|
||||
|
||||
RValue emitCall(const CIRGenFunctionInfo &funcInfo,
|
||||
const CIRGenCallee &callee, ReturnValueSlot returnValue,
|
||||
const CallArgList &args, cir::CIRCallOpInterface *callOp,
|
||||
|
||||
@ -13,6 +13,7 @@ add_clang_library(clangCIR
|
||||
CIRGenClass.cpp
|
||||
CIRGenCXXABI.cpp
|
||||
CIRGenCXXExpr.cpp
|
||||
CIRGenBuiltin.cpp
|
||||
CIRGenDecl.cpp
|
||||
CIRGenDeclOpenACC.cpp
|
||||
CIRGenExpr.cpp
|
||||
|
||||
78
clang/test/CIR/CodeGen/builtin_call.cpp
Normal file
78
clang/test/CIR/CodeGen/builtin_call.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
|
||||
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
|
||||
// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
|
||||
// RUN: %clang_cc1 -std=c++11 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
|
||||
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
|
||||
|
||||
constexpr extern int cx_var = __builtin_is_constant_evaluated();
|
||||
|
||||
// CIR: cir.global {{.*}} @cx_var = #cir.int<1> : !s32i
|
||||
// LLVM: @cx_var = {{.*}} i32 1
|
||||
// OGCG: @cx_var = {{.*}} i32 1
|
||||
|
||||
constexpr extern float cx_var_single = __builtin_huge_valf();
|
||||
|
||||
// CIR: cir.global {{.*}} @cx_var_single = #cir.fp<0x7F800000> : !cir.float
|
||||
// LLVM: @cx_var_single = {{.*}} float 0x7FF0000000000000
|
||||
// OGCG: @cx_var_single = {{.*}} float 0x7FF0000000000000
|
||||
|
||||
constexpr extern long double cx_var_ld = __builtin_huge_vall();
|
||||
|
||||
// CIR: cir.global {{.*}} @cx_var_ld = #cir.fp<0x7FFF8000000000000000> : !cir.long_double<!cir.f80>
|
||||
// LLVM: @cx_var_ld = {{.*}} x86_fp80 0xK7FFF8000000000000000
|
||||
// OGCG: @cx_var_ld = {{.*}} x86_fp80 0xK7FFF8000000000000000
|
||||
|
||||
int is_constant_evaluated() {
|
||||
return __builtin_is_constant_evaluated();
|
||||
}
|
||||
|
||||
// CIR: cir.func @_Z21is_constant_evaluatedv() -> !s32i
|
||||
// CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
|
||||
|
||||
// LLVM: define {{.*}}i32 @_Z21is_constant_evaluatedv()
|
||||
// LLVM: %[[MEM:.+]] = alloca i32
|
||||
// LLVM: store i32 0, ptr %[[MEM]]
|
||||
// LLVM: %[[RETVAL:.+]] = load i32, ptr %[[MEM]]
|
||||
// LLVM: ret i32 %[[RETVAL]]
|
||||
// LLVM: }
|
||||
|
||||
// OGCG: define {{.*}}i32 @_Z21is_constant_evaluatedv()
|
||||
// OGCG: ret i32 0
|
||||
// OGCG: }
|
||||
|
||||
long double constant_fp_builtin_ld() {
|
||||
return __builtin_fabsl(-0.1L);
|
||||
}
|
||||
|
||||
// CIR: cir.func @_Z22constant_fp_builtin_ldv() -> !cir.long_double<!cir.f80>
|
||||
// CIR: %[[PONE:.+]] = cir.const #cir.fp<1.000000e-01> : !cir.long_double<!cir.f80>
|
||||
|
||||
// LLVM: define {{.*}}x86_fp80 @_Z22constant_fp_builtin_ldv()
|
||||
// LLVM: %[[MEM:.+]] = alloca x86_fp80
|
||||
// LLVM: store x86_fp80 0xK3FFBCCCCCCCCCCCCCCCD, ptr %[[MEM]]
|
||||
// LLVM: %[[RETVAL:.+]] = load x86_fp80, ptr %[[MEM]]
|
||||
// LLVM: ret x86_fp80 %[[RETVAL]]
|
||||
// LLVM: }
|
||||
|
||||
// OGCG: define {{.*}}x86_fp80 @_Z22constant_fp_builtin_ldv()
|
||||
// OGCG: ret x86_fp80 0xK3FFBCCCCCCCCCCCCCCCD
|
||||
// OGCG: }
|
||||
|
||||
float constant_fp_builtin_single() {
|
||||
return __builtin_fabsf(-0.1f);
|
||||
}
|
||||
|
||||
// CIR: cir.func @_Z26constant_fp_builtin_singlev() -> !cir.float
|
||||
// CIR: %[[PONE:.+]] = cir.const #cir.fp<1.000000e-01> : !cir.float
|
||||
|
||||
// LLVM: define {{.*}}float @_Z26constant_fp_builtin_singlev()
|
||||
// LLVM: %[[MEM:.+]] = alloca float
|
||||
// LLVM: store float 0x3FB99999A0000000, ptr %[[MEM]]
|
||||
// LLVM: %[[RETVAL:.+]] = load float, ptr %[[MEM]]
|
||||
// LLVM: ret float %[[RETVAL]]
|
||||
// LLVM: }
|
||||
|
||||
// OGCG: define {{.*}}float @_Z26constant_fp_builtin_singlev()
|
||||
// OGCG: ret float 0x3FB99999A0000000
|
||||
// OGCG: }
|
||||
Loading…
x
Reference in New Issue
Block a user