-
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
Conversation
@llvm/pr-subscribers-clangir @llvm/pr-subscribers-clang Author: Morris Hafner (mmha) ChangesThis 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 ( It also adds the Patch is 35.78 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/142981.diff 12 Files Affected:
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 00878f7dd8ed7..fb891132b369a 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1858,6 +1858,40 @@ def FuncOp : CIR_Op<"func", [
let hasVerifier = 1;
}
+//===----------------------------------------------------------------------===//
+// LLVMIntrinsicCallOp
+//===----------------------------------------------------------------------===//
+
+def LLVMIntrinsicCallOp : CIR_Op<"llvm.intrinsic"> {
+ let summary = "Call to llvm intrinsic functions that is not defined in CIR";
+ let description = [{
+ The `cir.llvm.intrinsic` operation represents a call-like expression which has
+ a return type and arguments that map directly to an llvm intrinsic.
+ It only records its own name (`intrinsic_name`).
+ }];
+
+ let results = (outs Optional<CIR_AnyType>:$result);
+ let arguments = (ins
+ StrAttr:$intrinsic_name, Variadic<CIR_AnyType>:$arg_ops);
+
+ let skipDefaultBuilders = 1;
+
+ let assemblyFormat = [{
+ $intrinsic_name $arg_ops `:` functional-type($arg_ops, $result) attr-dict
+ }];
+
+ let builders = [
+ OpBuilder<(ins "mlir::StringAttr":$intrinsic_name, "mlir::Type":$resType,
+ CArg<"mlir::ValueRange", "{}">:$operands), [{
+ $_state.addAttribute("intrinsic_name", intrinsic_name);
+ $_state.addOperands(operands);
+ if (resType)
+ $_state.addTypes(resType);
+ }]>,
+ ];
+
+}
+
//===----------------------------------------------------------------------===//
// CallOp
//===----------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index da1946ed73746..1116b7e684371 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -216,6 +216,8 @@ struct MissingFeatures {
static bool peepholeProtection() { return false; }
static bool instrumentation() { return false; }
static bool cleanupAfterErrorDiags() { return false; }
+ static bool intrinsics() { return false; }
+ static bool attributeNoBuiltin() { return false; }
// Missing types
static bool dataMemberType() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
index 5620821a5375a..f7d00078f96dc 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.cpp
@@ -38,3 +38,25 @@ mlir::Value CIRGenBuilderTy::getArrayElement(mlir::Location arrayLocBegin,
const mlir::Type flatPtrTy = basePtr.getType();
return create<cir::PtrStrideOp>(arrayLocEnd, flatPtrTy, basePtr, idx);
}
+
+cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc,
+ llvm::APSInt intVal) {
+ bool isSigned = intVal.isSigned();
+ auto 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) {
+ auto width = intVal.getBitWidth();
+ cir::IntType t = getUIntNTy(width);
+ return getConstInt(loc, t, intVal.getZExtValue());
+}
+
+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));
+}
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
index 5f17b5d58acaa..4980e0d36478f 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuilder.h
+++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h
@@ -201,6 +201,19 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
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) {
+ assert((mlir::isa<cir::SingleType, cir::DoubleType>(t)) &&
+ "expected cir::SingleType or cir::DoubleType");
+ return create<cir::ConstantOp>(loc, getAttr<cir::FPAttr>(t, fpVal));
+ }
+
bool isInt8Ty(mlir::Type i) {
return i == typeCache.UInt8Ty || i == typeCache.SInt8Ty;
}
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
new file mode 100644
index 0000000000000..60a69f1182b5c
--- /dev/null
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -0,0 +1,453 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "clang/AST/Expr.h"
+#include "clang/CIR/Dialect/IR/CIRAttrs.h"
+#include "clang/CIR/Dialect/IR/CIRTypes.h"
+#include "clang/CIR/MissingFeatures.h"
+#include "llvm/IR/Intrinsics.h"
+
+#include "clang/AST/GlobalDecl.h"
+#include "clang/Basic/Builtins.h"
+
+#include "mlir/IR/BuiltinAttributes.h"
+#include "mlir/IR/Value.h"
+#include "mlir/Support/LLVM.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "llvm/Support/ErrorHandling.h"
+
+using namespace clang;
+using namespace clang::CIRGen;
+using namespace cir;
+using namespace llvm;
+
+static RValue emitLibraryCall(CIRGenFunction &cgf, const FunctionDecl *fd,
+ const CallExpr *e, mlir::Operation *calleeValue) {
+ CIRGenCallee callee = CIRGenCallee::forDirect(calleeValue, GlobalDecl(fd));
+ return cgf.emitCall(e->getCallee()->getType(), callee, e, ReturnValueSlot());
+}
+
+static mlir::Type
+decodeFixedType(CIRGenFunction &cgf,
+ ArrayRef<llvm::Intrinsic::IITDescriptor> &infos) {
+ using namespace llvm::Intrinsic;
+
+ auto *context = &cgf.getMLIRContext();
+ IITDescriptor descriptor = infos.front();
+ infos = infos.slice(1);
+
+ switch (descriptor.Kind) {
+ case IITDescriptor::Void:
+ return VoidType::get(context);
+ case IITDescriptor::Integer:
+ return IntType::get(context, descriptor.Integer_Width, /*isSigned=*/true);
+ case IITDescriptor::Float:
+ return SingleType::get(context);
+ case IITDescriptor::Double:
+ return DoubleType::get(context);
+ default:
+ cgf.cgm.errorNYI("intrinsic return types");
+ return VoidType::get(context);
+ }
+}
+
+// llvm::Intrinsics accepts only LLVMContext. We need to reimplement it here.
+static cir::FuncType getIntrinsicType(CIRGenFunction &cgf,
+ llvm::Intrinsic::ID id) {
+ using namespace llvm::Intrinsic;
+
+ SmallVector<IITDescriptor, 8> table;
+ getIntrinsicInfoTableEntries(id, table);
+
+ ArrayRef<IITDescriptor> tableRef = table;
+ mlir::Type resultTy = decodeFixedType(cgf, tableRef);
+
+ SmallVector<mlir::Type, 8> argTypes;
+ while (!tableRef.empty())
+ argTypes.push_back(decodeFixedType(cgf, tableRef));
+
+ return FuncType::get(argTypes, resultTy);
+}
+
+static mlir::Value emitTargetArchBuiltinExpr(CIRGenFunction *cgf,
+ unsigned builtinID,
+ const CallExpr *e,
+ ReturnValueSlot returnValue,
+ llvm::Triple::ArchType arch) {
+ return {};
+}
+
+mlir::Value CIRGenFunction::emitTargetBuiltinExpr(unsigned builtinID,
+ const CallExpr *e,
+ ReturnValueSlot returnValue) {
+ if (getContext().BuiltinInfo.isAuxBuiltinID(builtinID)) {
+ assert(getContext().getAuxTargetInfo() && "Missing aux target info");
+ return emitTargetArchBuiltinExpr(
+ this, getContext().BuiltinInfo.getAuxBuiltinID(builtinID), e,
+ returnValue, getContext().getAuxTargetInfo()->getTriple().getArch());
+ }
+
+ return emitTargetArchBuiltinExpr(this, builtinID, e, returnValue,
+ getTarget().getTriple().getArch());
+}
+
+mlir::Value CIRGenFunction::emitScalarOrConstFoldImmArg(unsigned iceArguments,
+ unsigned idx,
+ const CallExpr *e) {
+ mlir::Value arg = {};
+ if ((iceArguments & (1 << idx)) == 0) {
+ arg = emitScalarExpr(e->getArg(idx));
+ } else {
+ // If this is required to be a constant, constant fold it so that we
+ // know that the generated intrinsic gets a ConstantInt.
+ std::optional<llvm::APSInt> result =
+ e->getArg(idx)->getIntegerConstantExpr(getContext());
+ assert(result && "Expected argument to be a constant");
+ arg = builder.getConstInt(getLoc(e->getSourceRange()), *result);
+ }
+ return arg;
+}
+
+RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
+ const CallExpr *e,
+ ReturnValueSlot returnValue) {
+ const FunctionDecl *fd = gd.getDecl()->getAsFunction();
+
+ // 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. Clang Codegen uses the result value to make judgement
+ // of 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 resTy = convertType(e->getType());
+ return RValue::get(builder.getConstFP(getLoc(e->getExprLoc()), resTy,
+ result.Val.getFloat()));
+ }
+ }
+
+ // If current long-double semantics is IEEE 128-bit, replace math builtins
+ // of long-double with f128 equivalent.
+ // TODO: This mutation should also be applied to other targets other than PPC,
+ // after backend supports IEEE 128-bit style libcalls.
+ if (getTarget().getTriple().isPPC64() &&
+ &getTarget().getLongDoubleFormat() == &llvm::APFloat::IEEEquad()) {
+ cgm.errorNYI("long double builtin mutation");
+ }
+
+ // If the builtin has been declared explicitly with an assembler label,
+ // disable the specialized emitting below. Ideally we should communicate the
+ // rename in IR, or at least avoid generating the intrinsic calls that are
+ // likely to get lowered to the renamed library functions.
+ const unsigned builtinIDIfNoAsmLabel =
+ fd->hasAttr<AsmLabelAttr>() ? 0 : builtinID;
+
+ std::optional<bool> errnoOverriden;
+ // ErrnoOverriden is true if math-errno is overriden via the
+ // '#pragma float_control(precise, on)'. This pragma disables fast-math,
+ // which implies math-errno.
+ if (e->hasStoredFPFeatures()) {
+ FPOptionsOverride op = e->getFPFeatures();
+ if (op.hasMathErrnoOverride())
+ errnoOverriden = op.getMathErrnoOverride();
+ }
+ // True if 'atttibute__((optnone)) is used. This attibute overrides
+ // fast-math which implies math-errno.
+ bool optNone = curFuncDecl && curFuncDecl->hasAttr<OptimizeNoneAttr>();
+
+ // True if we are compiling at -O2 and errno has been disabled
+ // using the '#pragma float_control(precise, off)', and
+ // attribute opt-none hasn't been seen.
+ [[maybe_unused]] bool errnoOverridenToFalseWithOpt =
+ errnoOverriden.has_value() && !errnoOverriden.value() && !optNone &&
+ cgm.getCodeGenOpts().OptimizationLevel != 0;
+
+ // There are LLVM math intrinsics/instructions corresponding to math library
+ // functions except the LLVM op will never set errno while the math library
+ // might. Also, math builtins have the same semantics as their math library
+ // twins. Thus, we can transform math library and builtin calls to their
+ // LLVM counterparts if the call is marked 'const' (known to never set errno).
+ // In case FP exceptions are enabled, the experimental versions of the
+ // intrinsics model those.
+ [[maybe_unused]] bool constAlways =
+ getContext().BuiltinInfo.isConst(builtinID);
+
+ // There's a special case with the fma builtins where they are always const
+ // if the target environment is GNU or the target is OS is Windows and we're
+ // targeting the MSVCRT.dll environment.
+ // FIXME: This list can be become outdated. Need to find a way to get it some
+ // other way.
+ switch (builtinID) {
+ case Builtin::BI__builtin_fma:
+ case Builtin::BI__builtin_fmaf:
+ case Builtin::BI__builtin_fmal:
+ case Builtin::BIfma:
+ case Builtin::BIfmaf:
+ case Builtin::BIfmal:
+ cgm.errorNYI("FMA builtins");
+ break;
+ }
+
+ bool constWithoutErrnoAndExceptions =
+ getContext().BuiltinInfo.isConstWithoutErrnoAndExceptions(builtinID);
+ bool constWithoutExceptions =
+ getContext().BuiltinInfo.isConstWithoutExceptions(builtinID);
+
+ // ConstAttr is enabled in fast-math mode. In fast-math mode, math-errno is
+ // disabled.
+ // Math intrinsics are generated only when math-errno is disabled. Any pragmas
+ // or attributes that affect math-errno should prevent or allow math
+ // intrincs to be generated. Intrinsics are generated:
+ // 1- In fast math mode, unless math-errno is overriden
+ // via '#pragma float_control(precise, on)', or via an
+ // 'attribute__((optnone))'.
+ // 2- If math-errno was enabled on command line but overriden
+ // to false via '#pragma float_control(precise, off))' and
+ // 'attribute__((optnone))' hasn't been used.
+ // 3- If we are compiling with optimization and errno has been disabled
+ // via '#pragma float_control(precise, off)', and
+ // 'attribute__((optnone))' hasn't been used.
+
+ bool constWithoutErrnoOrExceptions =
+ constWithoutErrnoAndExceptions || constWithoutExceptions;
+ bool generateIntrinsics =
+ (constAlways && !optNone) ||
+ (!getLangOpts().MathErrno &&
+ !(errnoOverriden.has_value() && errnoOverriden.value()) && !optNone);
+ if (!generateIntrinsics) {
+ generateIntrinsics =
+ constWithoutErrnoOrExceptions && !constWithoutErrnoAndExceptions;
+ if (!generateIntrinsics)
+ generateIntrinsics =
+ constWithoutErrnoOrExceptions &&
+ (!getLangOpts().MathErrno &&
+ !(errnoOverriden.has_value() && errnoOverriden.value()) && !optNone);
+ if (!generateIntrinsics)
+ generateIntrinsics =
+ constWithoutErrnoOrExceptions && errnoOverridenToFalseWithOpt;
+ }
+
+ if (generateIntrinsics) {
+ assert(!cir::MissingFeatures::intrinsics());
+ return {};
+ }
+
+ switch (builtinIDIfNoAsmLabel) {
+ default:
+ break;
+ }
+
+ // If this is an alias for a lib function (e.g. __builtin_sin), emit
+ // the call using the normal call path, but using the unmangled
+ // version of the function name.
+ if (getContext().BuiltinInfo.isLibFunction(builtinID))
+ return emitLibraryCall(*this, fd, e,
+ cgm.getBuiltinLibFunction(fd, builtinID));
+
+ // If this is a predefined lib function (e.g. malloc), emit the call
+ // using exactly the normal call path.
+ if (getContext().BuiltinInfo.isPredefinedLibFunction(builtinID))
+ return emitLibraryCall(*this, fd, e,
+ emitScalarExpr(e->getCallee()).getDefiningOp());
+
+ // Check that a call to a target specific builtin has the correct target
+ // features.
+ // This is down here to avoid non-target specific builtins, however, if
+ // generic builtins start to require generic target features then we
+ // can move this up to the beginning of the function.
+ // checkTargetFeatures(E, FD);
+
+ if ([[maybe_unused]] unsigned vectorWidth =
+ getContext().BuiltinInfo.getRequiredVectorWidth(builtinID))
+ largestVectorWidth = std::max(largestVectorWidth, vectorWidth);
+
+ // See if we have a target specific intrinsic.
+ std::string name = getContext().BuiltinInfo.getName(builtinID);
+ Intrinsic::ID intrinsicID = Intrinsic::not_intrinsic;
+ StringRef prefix =
+ llvm::Triple::getArchTypePrefix(getTarget().getTriple().getArch());
+ if (!prefix.empty()) {
+ intrinsicID = Intrinsic::getIntrinsicForClangBuiltin(prefix.data(), name);
+ // NOTE we don't need to perform a compatibility flag check here since the
+ // intrinsics are declared in Builtins*.def via LANGBUILTIN which filter the
+ // MS builtins via ALL_MS_LANGUAGES and are filtered earlier.
+ if (intrinsicID == Intrinsic::not_intrinsic)
+ intrinsicID = Intrinsic::getIntrinsicForMSBuiltin(prefix.data(), name);
+ }
+
+ if (intrinsicID != Intrinsic::not_intrinsic) {
+ unsigned iceArguments = 0;
+ ASTContext::GetBuiltinTypeError error;
+ getContext().GetBuiltinType(builtinID, error, &iceArguments);
+ assert(error == ASTContext::GE_None && "Should not codegen an error");
+
+ llvm::StringRef name = llvm::Intrinsic::getName(intrinsicID);
+ // cir::LLVMIntrinsicCallOp expects intrinsic name to not have prefix
+ // "llvm." For example, `llvm.nvvm.barrier0` should be passed as
+ // `nvvm.barrier0`.
+ if (!name.consume_front("llvm."))
+ assert(false && "bad intrinsic name!");
+
+ cir::FuncType intrinsicType = getIntrinsicType(*this, intrinsicID);
+
+ SmallVector<mlir::Value> args;
+ for (unsigned i = 0; i < e->getNumArgs(); i++) {
+ mlir::Value arg = emitScalarOrConstFoldImmArg(iceArguments, i, e);
+ mlir::Type argType = arg.getType();
+ if (argType != intrinsicType.getInput(i)) {
+ // vector of pointers?
+ assert(!cir::MissingFeatures::addressSpace());
+ }
+
+ args.push_back(arg);
+ }
+
+ auto intrinsicCall = builder.create<cir::LLVMIntrinsicCallOp>(
+ getLoc(e->getExprLoc()), builder.getStringAttr(name),
+ intrinsicType.getReturnType(), args);
+
+ mlir::Type builtinReturnType = intrinsicCall.getResult().getType();
+ mlir::Type retTy = intrinsicType.getReturnType();
+
+ if (builtinReturnType != retTy) {
+ // vector of pointers?
+ if (isa<cir::PointerType>(retTy)) {
+ assert(!cir::MissingFeatures::addressSpace());
+ }
+ }
+
+ if (isa<cir::VoidType>(retTy))
+ return RValue::get(nullptr);
+
+ return RValue::get(intrinsicCall.getResult());
+ }
+
+ // Some target-specific builtins can have aggregate return values, e.g.
+ // __builtin_arm_mve_vld2q_u32. So if the result is an aggregate, force
+ // ReturnValue to be non-null, so that the target-specific emission code can
+ // always just emit into it.
+ cir::TypeEvaluationKind evalKind = getEvaluationKind(e->getType());
+ if (evalKind == cir::TEK_Aggregate && returnValue.isNull()) {
+ Address destPtr =
+ createMemTemp(e->getType(), getLoc(e->getSourceRange()), "agg.tmp");
+ returnValue = ReturnValueSlot(destPtr, false);
+ }
+
+ // Now see if we can emit a target-specific builtin.
+ if (auto v = emitTargetBuiltinExpr(builtinID, e, returnValue)) {
+ switch (evalKind) {
+ case cir::TEK_Scalar:
+ if (mlir::isa<cir::VoidType>(v.getType()))
+ return RValue::get(nullptr);
+ return RVal...
[truncated]
|
✅ With the latest revision this PR passed the C/C++ code formatter. |
def LLVMIntrinsicCallOp : CIR_Op<"llvm.intrinsic"> { | ||
let summary = "Call to llvm intrinsic functions that is not defined in CIR"; | ||
let description = [{ | ||
The `cir.llvm.intrinsic` operation represents a call-like expression which has |
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.
It looks like this line ran over 80 columns.
let description = [{ | ||
The `cir.llvm.intrinsic` operation represents a call-like expression which has | ||
a return type and arguments that map directly to an llvm intrinsic. | ||
It only records its own name (`intrinsic_name`). |
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.
This last sentence is a bit cryptic. Does it mean that it only records the base intrinsic name without polymorphic modifiers?
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.
I think the answer is in this comment:
// cir::LLVMIntrinsicCallOp expects intrinsic name to not have prefix
// "llvm." For example, `llvm.nvvm.barrier0` should be passed as
// `nvvm.barrier0`.
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc, | ||
llvm::APSInt intVal) { | ||
bool isSigned = intVal.isSigned(); | ||
auto width = intVal.getBitWidth(); |
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.
auto width = intVal.getBitWidth(); | |
unsigned width = intVal.getBitWidth(); |
|
||
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc, | ||
llvm::APInt intVal) { | ||
auto width = intVal.getBitWidth(); |
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.
auto width = intVal.getBitWidth(); | |
unsigned width = intVal.getBitWidth(); |
#include "clang/CIR/MissingFeatures.h" | ||
#include "llvm/IR/Intrinsics.h" | ||
|
||
#include "clang/AST/GlobalDecl.h" |
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.
The grouping here is odd. I think clang-format sorts headers that are grouped without empty lines between them, so it tolerates this, but I think it would make more sense to remove the empty lines.
|
||
cir::ConstantOp getConstFP(mlir::Location loc, mlir::Type t, | ||
llvm::APFloat fpVal) { | ||
assert((mlir::isa<cir::SingleType, cir::DoubleType>(t)) && |
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.
This doesn't seem like a good assumption, and it's crashing the incubator in this case: https://godbolt.org/z/Ycv1Gv7Y1.
// the constant. Clang Codegen uses the result value to make judgement | ||
// of 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. |
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.
The equivalent code in the classic codegen now looks like this:
if (Result.Val.isFloat())
return RValue::get(llvm::ConstantFP::get(getLLVMContext(),
Result.Val.getFloat()));
Can we make a version of getConstFP
that takes the APFloat result and deduces the type from that? The result should be the same. I think the type of result
will always be the same as the type of the expression, but it would be nice to have consistency between the int and float handling here.
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.
This isn't straightforward because we can't deduce the CIR type from an llvm::APFloat
if it used to be a long double type (or not). cir.long_double
is parameterized with an underlying type and the information whether it used to be long double
or say _Float64
is lost in the LLVM constant.
// after backend supports IEEE 128-bit style libcalls. | ||
if (getTarget().getTriple().isPPC64() && | ||
&getTarget().getLongDoubleFormat() == &llvm::APFloat::IEEEquad()) { | ||
cgm.errorNYI("long double builtin mutation"); |
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.
Should this be (not) happening before the constant folding above?
constWithoutErrnoOrExceptions && errnoOverridenToFalseWithOpt; | ||
} | ||
|
||
if (generateIntrinsics) { |
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.
The code to compute the generateIntrinsics
condition (from line 167 to here) was extremely tricky to get right, and I wouldn't be surprised if it changes as new pragmas are added. This really should be shared with classic codegen. Can you add a comment to that effect?
return {}; | ||
} | ||
|
||
switch (builtinIDIfNoAsmLabel) { |
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.
Is there an errorNYI missing here?
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.
There seems to be a LOT of code here that isn't in the tests? The only thing tested is no argument types, and just an int return type, yet there is a ton of builtin code to do a lot more than that.
I think this needs a lot more work to make sure we're properly testing this.
//===----------------------------------------------------------------------===// | ||
|
||
def LLVMIntrinsicCallOp : CIR_Op<"llvm.intrinsic"> { | ||
let summary = "Call to llvm intrinsic functions that is not defined in CIR"; |
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.
let summary = "Call to llvm intrinsic functions that is not defined in CIR"; | |
let summary = "Call to llvm intrinsic functions that are not defined in CIR"; |
@@ -201,6 +201,19 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy { | |||
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); |
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 from APInt
. Wonder if we should just implement one in terms of the other.
clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Outdated
const auto *fd = cast<FunctionDecl>(gd.getDecl()); | ||
|
||
if (unsigned builtinID = fd->getBuiltinID()) { | ||
std::string noBuiltinFD = ("no-builtin-" + fd->getName()).str(); |
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.
What is this for? And the line below?
@@ -47,6 +47,10 @@ class CIRGenFunction : public CIRGenTypeCache { | |||
/// is where the next operations will be introduced. | |||
CIRGenBuilderTy &builder; | |||
|
|||
/// Largest vector width used in ths function. Will be used to create a |
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.
/// Largest vector width used in ths function. Will be used to create a | |
/// Largest vector width used in this function. Will be used to create a |
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.
This PR needs to either remove a lot of code or add a lot more test cases.
// In case FP exceptions are enabled, the experimental versions of the | ||
// intrinsics model those. | ||
[[maybe_unused]] bool constAlways = | ||
getContext().BuiltinInfo.isConst(builtinID); |
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 use getContext().BuiltinInfo
often enough in this function that it would be nice to save a reference to it rather than making the call each time.
|
||
if (generateIntrinsics) { | ||
assert(!cir::MissingFeatures::intrinsics()); | ||
return {}; |
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.
It would probably be better to omit all of the code above that computes generateIntrinsics
until we upstream the implementation, and just fall through to generating the library call below. Returning here blocks compilation of things that could be handled just by emitting the library call.
let description = [{ | ||
The `cir.llvm.intrinsic` operation represents a call-like expression which has | ||
a return type and arguments that map directly to an llvm intrinsic. | ||
It only records its own name (`intrinsic_name`). |
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.
I think the answer is in this comment:
// cir::LLVMIntrinsicCallOp expects intrinsic name to not have prefix
// "llvm." For example, `llvm.nvvm.barrier0` should be passed as
// `nvvm.barrier0`.
if (!name.consume_front("llvm.")) | ||
assert(false && "bad intrinsic name!"); | ||
|
||
cir::FuncType intrinsicType = getIntrinsicType(*this, intrinsicID); |
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.
This won't be correct if the intrinsic is overloaded. The call to getName
on line 306 will also assert if the intrinsic is overloaded, but it would be helpful to do something with that case explicitly here. I think most cases will be handled above in the final implementation (which is enormous in classic codegen), but we should emit an errorNYI if we get here with an overloaded intrinsic. The argType != intrinsicType.getInput(i)
condition on line 319 can be caused by an overloaded intrinsic.
mlir::Type argType = arg.getType(); | ||
if (argType != intrinsicType.getInput(i)) { | ||
// vector of pointers? | ||
assert(!cir::MissingFeatures::addressSpace()); |
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.
This needs an errorNYI. We're going to crash if we get here.
mlir::Type builtinReturnType = intrinsicCall.getResult().getType(); | ||
mlir::Type retTy = intrinsicType.getReturnType(); | ||
|
||
if (builtinReturnType != retTy) { |
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.
errorNYI
} | ||
|
||
// Now see if we can emit a target-specific builtin. | ||
if (auto v = emitTargetBuiltinExpr(builtinID, e, returnValue)) { |
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.
No auto here.
aixLongDouble64Builtins.end()) | ||
name = aixLongDouble64Builtins[builtinID]; | ||
else | ||
name = astContext.BuiltinInfo.getName(builtinID).substr(10); |
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.
I think you'd be better off leaving everything in this function except this case as "NYI". We don't need the rest of it. This seems to be making some assumption about the format of the string returned by BuiltinInfo.getName()
. I'd like to see that verified. As it stands, 10
is very much a magic number.
I'm guessing a bit here, but I think it should be something like this:
const char* builtinPrefix = "__builtin_";
StringRef name = astContext.BuiltinInfo.getName(builtinID);
assert(builtinName.startsWith(builtinPrefix));
name.consume_front(builtinPrefix);
Or that last line could be
name.substr(strlen(builtinPrefix));
clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Outdated
const auto *fd = cast<FunctionDecl>(gd.getDecl()); | ||
|
||
if (unsigned builtinID = fd->getBuiltinID()) { | ||
std::string noBuiltinFD = ("no-builtin-" + fd->getName()).str(); |
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.
These two variables aren't used yet. They should be deleted until the implementation that references them is upstreamed.
clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Outdated
std::string noBuiltinFD = ("no-builtin-" + fd->getName()).str(); | ||
std::string noBuiltins = "no-builtins"; | ||
|
||
auto *a = fd->getAttr<AsmLabelAttr>(); |
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 should either have an errorNYI for AsmLabelAttr or tests for it.
@@ -201,6 +201,19 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy { | |||
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); |
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 from APInt
. Wonder if we should just implement one in terms of the other.
|
||
cir::ConstantOp CIRGenBuilderTy::getConstInt(mlir::Location loc, | ||
llvm::APInt intVal) { | ||
unsigned width = intVal.getBitWidth(); |
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.
unsigned width = intVal.getBitWidth(); | |
return getConstInt(loc, llvm::APSInt(intVal)); |
|
||
cir::ConstantOp getConstInt(mlir::Location loc, mlir::Type t, uint64_t c); | ||
|
||
mlir::Type getFPType(llvm::fltSemantics const &sem) { |
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.
Where is this being used in this patch?
} | ||
} | ||
|
||
cir::ConstantOp getConstFP(mlir::Location loc, llvm::APFloat fpVal) { |
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.
This too?
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 comment
The 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 comment
The 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).
// 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 comment
The 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.
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 comment
The 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).
@@ -1004,8 +1004,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 comment
The 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.
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.
lgtm
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.
LGTM except 2 nits
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). It also adds the LLVMIntrinsicCallOp instruction to the CIR dialect.
…n calls - Address review feedback
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 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 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). A few builtins that receive special handling like FMA are still kept in this patch and explicitly marked as NYI so we don't have to backtrack those later on. This increased the patch size unfortunately.It also adds the
LLVMIntrinsicCallOp
instruction to the CIR dialect.