-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[CIR] Upstream minimal builtin function call support #142981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1faee59
b46acbf
401dd62
2b4b002
0054f04
7c58c49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have a test for one that has a floating point result? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could test float with __builtin_huge_val or builtin_inf (plus [f|l|fp16|fp128] suffixes). |
||
// 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()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe you've removed all instances of this MissingFeature. It should be removed from the header. |
||
// 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"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We still seem to have a lot of code not tested in here? I don't see any FP stuff here, for example. |
||
|
||
// 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: } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we overloading this based on APSInt vs APInt? They are in the same inheritance tree.. We should probably only do APInt.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to handle APSInt to get signed constants represented correctly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, we probably only have to handle
APSInt
. It has an (explicit) ctor fromAPInt
. Wonder if we should just implement one in terms of the other.