//===----------------------------------------------------------------------===// // // 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 "CIRGenConstantEmitter.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 "clang/Basic/Builtins.h" #include "clang/CIR/MissingFeatures.h" #include "llvm/Support/ErrorHandling.h" using namespace clang; using namespace clang::CIRGen; 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()); } template static RValue emitBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e, bool poisonZero = false) { assert(!cir::MissingFeatures::builtinCheckKind()); mlir::Value arg = cgf.emitScalarExpr(e->getArg(0)); CIRGenBuilderTy &builder = cgf.getBuilder(); Op op; if constexpr (std::is_same_v || std::is_same_v) op = builder.create(cgf.getLoc(e->getSourceRange()), arg, poisonZero); else op = builder.create(cgf.getLoc(e->getSourceRange()), arg); mlir::Value result = op.getResult(); mlir::Type exprTy = cgf.convertType(e->getType()); if (exprTy != result.getType()) result = builder.createIntCast(result, exprTy); return RValue::get(result); } RValue CIRGenFunction::emitRotate(const CallExpr *e, bool isRotateLeft) { mlir::Value input = emitScalarExpr(e->getArg(0)); mlir::Value amount = emitScalarExpr(e->getArg(1)); // TODO(cir): MSVC flavor bit rotate builtins use different types for input // and amount, but cir.rotate requires them to have the same type. Cast amount // to the type of input when necessary. assert(!cir::MissingFeatures::msvcBuiltins()); auto r = builder.create(getLoc(e->getSourceRange()), input, amount, isRotateLeft); return RValue::get(r); } template static RValue emitUnaryMaybeConstrainedFPBuiltin(CIRGenFunction &cgf, const CallExpr &e) { mlir::Value arg = cgf.emitScalarExpr(e.getArg(0)); assert(!cir::MissingFeatures::cgFPOptionsRAII()); assert(!cir::MissingFeatures::fpConstraints()); auto call = Operation::create(cgf.getBuilder(), arg.getLoc(), arg.getType(), arg); return RValue::get(call->getResult(0)); } template static RValue emitUnaryFPBuiltin(CIRGenFunction &cgf, const CallExpr &e) { mlir::Value arg = cgf.emitScalarExpr(e.getArg(0)); auto call = Operation::create(cgf.getBuilder(), arg.getLoc(), arg.getType(), arg); return RValue::get(call->getResult(0)); } RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID, const CallExpr *e, ReturnValueSlot returnValue) { mlir::Location loc = getLoc(e->getSourceRange()); // 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(loc, 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(loc, type, result.Val.getFloat())); } } const FunctionDecl *fd = gd.getDecl()->getAsFunction(); assert(!cir::MissingFeatures::builtinCallF128()); // 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. unsigned builtinIDIfNoAsmLabel = fd->hasAttr() ? 0 : builtinID; assert(!cir::MissingFeatures::builtinCallMathErrno()); assert(!cir::MissingFeatures::builtinCall()); switch (builtinIDIfNoAsmLabel) { default: break; // C stdarg builtins. case Builtin::BI__builtin_stdarg_start: case Builtin::BI__builtin_va_start: case Builtin::BI__va_start: { mlir::Value vaList = builtinID == Builtin::BI__va_start ? emitScalarExpr(e->getArg(0)) : emitVAListRef(e->getArg(0)).getPointer(); mlir::Value count = emitScalarExpr(e->getArg(1)); emitVAStart(vaList, count); return {}; } case Builtin::BI__builtin_va_end: emitVAEnd(emitVAListRef(e->getArg(0)).getPointer()); return {}; case Builtin::BIalloca: case Builtin::BI_alloca: case Builtin::BI__builtin_alloca_uninitialized: case Builtin::BI__builtin_alloca: { // Get alloca size input mlir::Value size = emitScalarExpr(e->getArg(0)); // The alignment of the alloca should correspond to __BIGGEST_ALIGNMENT__. const TargetInfo &ti = getContext().getTargetInfo(); const CharUnits suitableAlignmentInBytes = getContext().toCharUnitsFromBits(ti.getSuitableAlign()); // Emit the alloca op with type `u8 *` to match the semantics of // `llvm.alloca`. We later bitcast the type to `void *` to match the // semantics of C/C++ // FIXME(cir): It may make sense to allow AllocaOp of type `u8` to return a // pointer of type `void *`. This will require a change to the allocaOp // verifier. mlir::Value allocaAddr = builder.createAlloca( getLoc(e->getSourceRange()), builder.getUInt8PtrTy(), builder.getUInt8Ty(), "bi_alloca", suitableAlignmentInBytes, size); // Initialize the allocated buffer if required. if (builtinID != Builtin::BI__builtin_alloca_uninitialized) { // Initialize the alloca with the given size and alignment according to // the lang opts. Only the trivial non-initialization is supported for // now. switch (getLangOpts().getTrivialAutoVarInit()) { case LangOptions::TrivialAutoVarInitKind::Uninitialized: // Nothing to initialize. break; case LangOptions::TrivialAutoVarInitKind::Zero: case LangOptions::TrivialAutoVarInitKind::Pattern: cgm.errorNYI("trivial auto var init"); break; } } // An alloca will always return a pointer to the alloca (stack) address // space. This address space need not be the same as the AST / Language // default (e.g. in C / C++ auto vars are in the generic address space). At // the AST level this is handled within CreateTempAlloca et al., but for the // builtin / dynamic alloca we have to handle it here. assert(!cir::MissingFeatures::addressSpace()); // Bitcast the alloca to the expected type. return RValue::get( builder.createBitcast(allocaAddr, builder.getVoidPtrTy())); } case Builtin::BIcos: case Builtin::BIcosf: case Builtin::BIcosl: case Builtin::BI__builtin_cos: case Builtin::BI__builtin_cosf: case Builtin::BI__builtin_cosf16: case Builtin::BI__builtin_cosl: case Builtin::BI__builtin_cosf128: assert(!cir::MissingFeatures::fastMathFlags()); return emitUnaryMaybeConstrainedFPBuiltin(*this, *e); case Builtin::BIfabs: case Builtin::BIfabsf: case Builtin::BIfabsl: case Builtin::BI__builtin_fabs: case Builtin::BI__builtin_fabsf: case Builtin::BI__builtin_fabsf16: case Builtin::BI__builtin_fabsl: case Builtin::BI__builtin_fabsf128: return emitUnaryMaybeConstrainedFPBuiltin(*this, *e); case Builtin::BI__assume: case Builtin::BI__builtin_assume: { if (e->getArg(0)->HasSideEffects(getContext())) return RValue::get(nullptr); mlir::Value argValue = emitCheckedArgForAssume(e->getArg(0)); builder.create(loc, argValue); return RValue::get(nullptr); } case Builtin::BI__builtin_assume_separate_storage: { mlir::Value value0 = emitScalarExpr(e->getArg(0)); mlir::Value value1 = emitScalarExpr(e->getArg(1)); builder.create(loc, value0, value1); return RValue::get(nullptr); } case Builtin::BI__builtin_assume_aligned: { const Expr *ptrExpr = e->getArg(0); mlir::Value ptrValue = emitScalarExpr(ptrExpr); mlir::Value offsetValue = (e->getNumArgs() > 2) ? emitScalarExpr(e->getArg(2)) : nullptr; std::optional alignment = e->getArg(1)->getIntegerConstantExpr(getContext()); assert(alignment.has_value() && "the second argument to __builtin_assume_aligned must be an " "integral constant expression"); mlir::Value result = emitAlignmentAssumption(ptrValue, ptrExpr, ptrExpr->getExprLoc(), alignment->getSExtValue(), offsetValue); return RValue::get(result); } case Builtin::BI__builtin_complex: { mlir::Value real = emitScalarExpr(e->getArg(0)); mlir::Value imag = emitScalarExpr(e->getArg(1)); mlir::Value complex = builder.createComplexCreate(loc, real, imag); return RValue::getComplex(complex); } case Builtin::BI__builtin_creal: case Builtin::BI__builtin_crealf: case Builtin::BI__builtin_creall: case Builtin::BIcreal: case Builtin::BIcrealf: case Builtin::BIcreall: { mlir::Value complex = emitComplexExpr(e->getArg(0)); mlir::Value real = builder.createComplexReal(loc, complex); return RValue::get(real); } case Builtin::BI__builtin_cimag: case Builtin::BI__builtin_cimagf: case Builtin::BI__builtin_cimagl: case Builtin::BIcimag: case Builtin::BIcimagf: case Builtin::BIcimagl: { mlir::Value complex = emitComplexExpr(e->getArg(0)); mlir::Value imag = builder.createComplexImag(loc, complex); return RValue::get(imag); } case Builtin::BI__builtin_conj: case Builtin::BI__builtin_conjf: case Builtin::BI__builtin_conjl: case Builtin::BIconj: case Builtin::BIconjf: case Builtin::BIconjl: { mlir::Value complex = emitComplexExpr(e->getArg(0)); mlir::Value conj = builder.createUnaryOp(getLoc(e->getExprLoc()), cir::UnaryOpKind::Not, complex); return RValue::getComplex(conj); } case Builtin::BI__builtin_clrsb: case Builtin::BI__builtin_clrsbl: case Builtin::BI__builtin_clrsbll: return emitBuiltinBitOp(*this, e); case Builtin::BI__builtin_ctzs: case Builtin::BI__builtin_ctz: case Builtin::BI__builtin_ctzl: case Builtin::BI__builtin_ctzll: case Builtin::BI__builtin_ctzg: assert(!cir::MissingFeatures::builtinCheckKind()); return emitBuiltinBitOp(*this, e, /*poisonZero=*/true); case Builtin::BI__builtin_clzs: case Builtin::BI__builtin_clz: case Builtin::BI__builtin_clzl: case Builtin::BI__builtin_clzll: case Builtin::BI__builtin_clzg: assert(!cir::MissingFeatures::builtinCheckKind()); return emitBuiltinBitOp(*this, e, /*poisonZero=*/true); case Builtin::BI__builtin_ffs: case Builtin::BI__builtin_ffsl: case Builtin::BI__builtin_ffsll: return emitBuiltinBitOp(*this, e); case Builtin::BI__builtin_parity: case Builtin::BI__builtin_parityl: case Builtin::BI__builtin_parityll: return emitBuiltinBitOp(*this, e); case Builtin::BI__lzcnt16: case Builtin::BI__lzcnt: case Builtin::BI__lzcnt64: assert(!cir::MissingFeatures::builtinCheckKind()); return emitBuiltinBitOp(*this, e, /*poisonZero=*/false); case Builtin::BI__popcnt16: case Builtin::BI__popcnt: case Builtin::BI__popcnt64: case Builtin::BI__builtin_popcount: case Builtin::BI__builtin_popcountl: case Builtin::BI__builtin_popcountll: case Builtin::BI__builtin_popcountg: return emitBuiltinBitOp(*this, e); case Builtin::BI__builtin_expect: case Builtin::BI__builtin_expect_with_probability: { mlir::Value argValue = emitScalarExpr(e->getArg(0)); mlir::Value expectedValue = emitScalarExpr(e->getArg(1)); mlir::FloatAttr probAttr; if (builtinIDIfNoAsmLabel == Builtin::BI__builtin_expect_with_probability) { llvm::APFloat probability(0.0); const Expr *probArg = e->getArg(2); [[maybe_unused]] bool evalSucceeded = probArg->EvaluateAsFloat(probability, cgm.getASTContext()); assert(evalSucceeded && "probability should be able to evaluate as float"); bool loseInfo = false; // ignored probability.convert(llvm::APFloat::IEEEdouble(), llvm::RoundingMode::Dynamic, &loseInfo); probAttr = mlir::FloatAttr::get(mlir::Float64Type::get(&getMLIRContext()), probability); } auto result = builder.create( loc, argValue.getType(), argValue, expectedValue, probAttr); return RValue::get(result); } case Builtin::BI__builtin_bswap16: case Builtin::BI__builtin_bswap32: case Builtin::BI__builtin_bswap64: case Builtin::BI_byteswap_ushort: case Builtin::BI_byteswap_ulong: case Builtin::BI_byteswap_uint64: { mlir::Value arg = emitScalarExpr(e->getArg(0)); return RValue::get(builder.create(loc, arg)); } case Builtin::BI__builtin_bitreverse8: case Builtin::BI__builtin_bitreverse16: case Builtin::BI__builtin_bitreverse32: case Builtin::BI__builtin_bitreverse64: { mlir::Value arg = emitScalarExpr(e->getArg(0)); return RValue::get(builder.create(loc, arg)); } case Builtin::BI__builtin_rotateleft8: case Builtin::BI__builtin_rotateleft16: case Builtin::BI__builtin_rotateleft32: case Builtin::BI__builtin_rotateleft64: return emitRotate(e, /*isRotateLeft=*/true); case Builtin::BI__builtin_rotateright8: case Builtin::BI__builtin_rotateright16: case Builtin::BI__builtin_rotateright32: case Builtin::BI__builtin_rotateright64: return emitRotate(e, /*isRotateLeft=*/false); case Builtin::BI__builtin_return_address: case Builtin::BI__builtin_frame_address: { mlir::Location loc = getLoc(e->getExprLoc()); llvm::APSInt level = e->getArg(0)->EvaluateKnownConstInt(getContext()); if (builtinID == Builtin::BI__builtin_return_address) { return RValue::get(cir::ReturnAddrOp::create( builder, loc, builder.getConstAPInt(loc, builder.getUInt32Ty(), level))); } return RValue::get(cir::FrameAddrOp::create( builder, loc, builder.getConstAPInt(loc, builder.getUInt32Ty(), level))); } case Builtin::BI__builtin_trap: emitTrap(loc, /*createNewBlock=*/true); return RValue::get(nullptr); case Builtin::BI__builtin_unreachable: emitUnreachable(e->getExprLoc(), /*createNewBlock=*/true); return RValue::get(nullptr); case Builtin::BI__builtin_elementwise_acos: return emitUnaryFPBuiltin(*this, *e); case Builtin::BI__builtin_elementwise_asin: return emitUnaryFPBuiltin(*this, *e); case Builtin::BI__builtin_elementwise_atan: return emitUnaryFPBuiltin(*this, *e); case Builtin::BI__builtin_elementwise_cos: return emitUnaryFPBuiltin(*this, *e); } // 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)); cgm.errorNYI(e->getSourceRange(), "unimplemented builtin call"); return getUndefRValue(e->getType()); } /// Given a builtin id for a function like "__builtin_fabsf", return a Function* /// for "fabsf". cir::FuncOp CIRGenModule::getBuiltinLibFunction(const FunctionDecl *fd, unsigned builtinID) { assert(astContext.BuiltinInfo.isLibFunction(builtinID)); // Get the name, skip over the __builtin_ prefix (if necessary). We may have // to build this up so provide a small stack buffer to handle the vast // majority of names. llvm::SmallString<64> name; assert(!cir::MissingFeatures::asmLabelAttr()); name = astContext.BuiltinInfo.getName(builtinID).substr(10); GlobalDecl d(fd); mlir::Type type = convertType(fd->getType()); return getOrCreateCIRFunction(name, type, d, /*forVTable=*/false); } mlir::Value CIRGenFunction::emitCheckedArgForAssume(const Expr *e) { mlir::Value argValue = evaluateExprAsBool(e); if (!sanOpts.has(SanitizerKind::Builtin)) return argValue; assert(!cir::MissingFeatures::sanitizers()); cgm.errorNYI(e->getSourceRange(), "emitCheckedArgForAssume: sanitizers are NYI"); return {}; } void CIRGenFunction::emitVAStart(mlir::Value vaList, mlir::Value count) { // LLVM codegen casts to *i8, no real gain on doing this for CIRGen this // early, defer to LLVM lowering. cir::VAStartOp::create(builder, vaList.getLoc(), vaList, count); } void CIRGenFunction::emitVAEnd(mlir::Value vaList) { cir::VAEndOp::create(builder, vaList.getLoc(), vaList); } // FIXME(cir): This completely abstracts away the ABI with a generic CIR Op. By // default this lowers to llvm.va_arg which is incomplete and not ABI-compliant // on most targets so cir.va_arg will need some ABI handling in LoweringPrepare mlir::Value CIRGenFunction::emitVAArg(VAArgExpr *ve) { assert(!cir::MissingFeatures::msabi()); assert(!cir::MissingFeatures::vlas()); mlir::Location loc = cgm.getLoc(ve->getExprLoc()); mlir::Type type = convertType(ve->getType()); mlir::Value vaList = emitVAListRef(ve->getSubExpr()).getPointer(); return cir::VAArgOp::create(builder, loc, type, vaList); }