https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/130164
>From 69c7104391ff9b6ae1dbcb6275a3070da4f8dd02 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Tue, 4 Mar 2025 10:21:24 -0800 Subject: [PATCH 1/2] [CIR] Emit init of local variables Local variable initialization was previously being ignored. This change adds support for initialization of scalar variables with constant values and introduces the constant emitter framework. --- .../CIR/Dialect/Builder/CIRBaseBuilder.h | 4 + .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 14 + clang/include/clang/CIR/MissingFeatures.h | 10 +- clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h | 98 ++++++ clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 180 +++++++++- clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 72 +++- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 322 ++++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 109 +++++- clang/lib/CIR/CodeGen/CIRGenModule.h | 14 + clang/lib/CIR/CodeGen/CMakeLists.txt | 1 + clang/test/CIR/CodeGen/basic.cpp | 15 +- clang/test/CIR/CodeGen/local-vars.cpp | 65 ++++ clang/test/CIR/Lowering/basic.cpp | 8 +- clang/test/CIR/Lowering/local-vars.cpp | 55 +++ 14 files changed, 934 insertions(+), 33 deletions(-) create mode 100644 clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp create mode 100644 clang/test/CIR/CodeGen/local-vars.cpp create mode 100644 clang/test/CIR/Lowering/local-vars.cpp diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index b65797e40d5f9..017ae0c53a984 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -26,6 +26,10 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { CIRBaseBuilderTy(mlir::MLIRContext &mlirContext) : mlir::OpBuilder(&mlirContext) {} + cir::ConstantOp getConstant(mlir::Location loc, mlir::TypedAttr attr) { + return create<cir::ConstantOp>(loc, attr.getType(), attr); + } + cir::ConstantOp getBool(bool state, mlir::Location loc) { return create<cir::ConstantOp>(loc, getBoolTy(), getCIRBoolAttr(state)); } diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index ece04c225e322..7b3741de29075 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -54,6 +54,20 @@ def CIR_BoolAttr : CIR_Attr<"Bool", "bool", [TypedAttrInterface]> { }]; } +//===----------------------------------------------------------------------===// +// ZeroAttr +//===----------------------------------------------------------------------===// + +def ZeroAttr : CIR_Attr<"Zero", "zero", [TypedAttrInterface]> { + let summary = "Attribute to represent zero initialization"; + let description = [{ + The ZeroAttr is used to indicate zero initialization on structs. + }]; + + let parameters = (ins AttributeSelfTypeParameter<"">:$type); + let assemblyFormat = [{}]; +} + //===----------------------------------------------------------------------===// // UndefAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 6f845b7689e51..d20cd0560a7c1 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -41,16 +41,17 @@ struct MissingFeatures { static bool supportComdat() { return false; } // Load/store attributes - static bool opLoadThreadLocal() { return false; } + static bool opLoadStoreThreadLocal() { return false; } static bool opLoadEmitScalarRangeCheck() { return false; } static bool opLoadBooleanRepresentation() { return false; } static bool opLoadStoreTbaa() { return false; } static bool opLoadStoreMemOrder() { return false; } static bool opLoadStoreVolatile() { return false; } static bool opLoadStoreAlignment() { return false; } + static bool opLoadStoreAtomic() { return false; } + static bool opLoadStoreObjC() { return false; } // AllocaOp handling - static bool opAllocaVarDeclContext() { return false; } static bool opAllocaStaticLocal() { return false; } static bool opAllocaNonGC() { return false; } static bool opAllocaImpreciseLifetime() { return false; } @@ -61,6 +62,7 @@ struct MissingFeatures { static bool opAllocaReference() { return false; } static bool opAllocaAnnotations() { return false; } static bool opAllocaDynAllocSize() { return false; } + static bool opAllocaCaptureByInit() { return false; } // FuncOp handling static bool opFuncOpenCLKernelMetadata() { return false; } @@ -76,6 +78,10 @@ struct MissingFeatures { static bool constructABIArgDirectExtend() { return false; } static bool opGlobalViewAttr() { return false; } static bool lowerModeOptLevel() { return false; } + static bool opTBAA() { return false; } + static bool objCLifetime() { return false; } + static bool emitNullabilityCheck() { return false; } + static bool astVarDeclInterface() { return false; } }; } // namespace cir diff --git a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h new file mode 100644 index 0000000000000..e29149c2e70f1 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// A helper class for emitting expressions and values as cir::ConstantOp +// and as initializers for global variables. +// +// Note: this is based on clang's LLVM IR codegen in ConstantEmitter.h, reusing +// this class interface makes it easier move forward with bringing CIR codegen +// to completion. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_LIB_CIR_CODEGEN_CIRGENCONSTANTEMITTER_H +#define CLANG_LIB_CIR_CODEGEN_CIRGENCONSTANTEMITTER_H + +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "llvm/ADT/SmallVector.h" + +namespace clang::CIRGen { + +class ConstantEmitter { +public: + CIRGenModule &cgm; + const CIRGenFunction *cgf; + +private: + bool abstract = false; + + /// Whether we're in a constant context. + bool inConstantContext = false; + +public: + /// Initialize this emission in the context of the given function. + /// Use this if the expression might contain contextual references like + /// block addresses or PredefinedExprs. + ConstantEmitter(CIRGenFunction &cgf) : cgm(cgf.cgm), cgf(&cgf) {} + + ConstantEmitter(const ConstantEmitter &other) = delete; + ConstantEmitter &operator=(const ConstantEmitter &other) = delete; + + // All of the "abstract" emission methods below permit the emission to + // be immediately discarded without finalizing anything. Therefore, they + // must also promise not to do anything that will, in the future, require + // finalization: + // + // - using the CGF (if present) for anything other than establishing + // semantic context; for example, an expression with ignored + // side-effects must not be emitted as an abstract expression + // + // - doing anything that would not be safe to duplicate within an + // initializer or to propagate to another context; for example, + // side effects, or emitting an initialization that requires a + // reference to its current location. + mlir::Attribute emitForMemory(mlir::Attribute c, QualType t); + + /// Emit the result of the given expression as an abstract constant, + /// asserting that it succeeded. This is only safe to do when the + /// expression is known to be a constant expression with either a fairly + /// simple type or a known simple form. + mlir::Attribute emitAbstract(SourceLocation loc, const APValue &value, + QualType t); + + mlir::Attribute tryEmitConstantExpr(const ConstantExpr *CE); + + // These are private helper routines of the constant emitter that + // can't actually be private because things are split out into helper + // functions and classes. + + mlir::Attribute tryEmitPrivateForVarInit(const VarDecl &d); + + mlir::Attribute tryEmitPrivate(const APValue &value, QualType destType); + mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType t); + + /// Try to emit the initializer of the given declaration as an abstract + /// constant. + mlir::Attribute tryEmitAbstractForInitializer(const VarDecl &d); + +private: + struct AbstractState { + bool oldValue; + }; + AbstractState pushAbstract() { + AbstractState saved = {abstract}; + abstract = true; + return saved; + } + mlir::Attribute validateAndPopAbstract(mlir::Attribute C, AbstractState save); +}; + +} // namespace clang::CIRGen + +#endif // CLANG_LIB_CIR_CODEGEN_CIRGENCONSTANTEMITTER_H diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index 406026b0b9f27..ed42cd6bd2c32 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -10,23 +10,29 @@ // //===----------------------------------------------------------------------===// +#include "CIRGenConstantEmitter.h" #include "CIRGenFunction.h" +#include "mlir/IR/Location.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/CIR/MissingFeatures.h" using namespace clang; using namespace clang::CIRGen; -void CIRGenFunction::emitAutoVarAlloca(const VarDecl &d) { +CIRGenFunction::AutoVarEmission +CIRGenFunction::emitAutoVarAlloca(const VarDecl &d) { QualType ty = d.getType(); if (ty.getAddressSpace() != LangAS::Default) cgm.errorNYI(d.getSourceRange(), "emitAutoVarAlloca: address space"); - auto loc = getLoc(d.getSourceRange()); + mlir::Location loc = getLoc(d.getSourceRange()); - if (d.isEscapingByref()) + CIRGenFunction::AutoVarEmission emission(d); + emission.IsEscapingByRef = d.isEscapingByref(); + if (emission.IsEscapingByRef) cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: decl escaping by reference"); @@ -46,21 +52,124 @@ void CIRGenFunction::emitAutoVarAlloca(const VarDecl &d) { address = createTempAlloca(allocaTy, alignment, loc, d.getName()); declare(address.getPointer(), &d, ty, getLoc(d.getSourceRange()), alignment); + emission.Addr = address; setAddrOfLocalVar(&d, address); + + return emission; +} + +/// Determine whether the given initializer is trivial in the sense +/// that it requires no code to be generated. +bool CIRGenFunction::isTrivialInitializer(const Expr *init) { + if (!init) + return true; + + if (const CXXConstructExpr *construct = dyn_cast<CXXConstructExpr>(init)) + if (CXXConstructorDecl *constructor = construct->getConstructor()) + if (constructor->isTrivial() && constructor->isDefaultConstructor() && + !construct->requiresZeroInitialization()) + return true; + + return false; } -void CIRGenFunction::emitAutoVarInit(const clang::VarDecl &d) { +void CIRGenFunction::emitAutoVarInit( + const CIRGenFunction::AutoVarEmission &emission) { + const VarDecl &d = *emission.Variable; + QualType type = d.getType(); // If this local has an initializer, emit it now. const Expr *init = d.getInit(); - if (init || !type.isPODType(getContext())) { - cgm.errorNYI(d.getSourceRange(), "emitAutoVarInit"); + if (!type.isPODType(getContext())) { + cgm.errorNYI(d.getSourceRange(), "emitAutoVarInit: non-POD type"); + return; + } + + const Address addr = emission.Addr; + + // Check whether this is a byref variable that's potentially + // captured and moved by its own initializer. If so, we'll need to + // emit the initializer first, then copy into the variable. + assert(!cir::MissingFeatures::opAllocaCaptureByInit()); + + // Note: constexpr already initializes everything correctly. + LangOptions::TrivialAutoVarInitKind trivialAutoVarInit = + (d.isConstexpr() + ? LangOptions::TrivialAutoVarInitKind::Uninitialized + : (d.getAttr<UninitializedAttr>() + ? LangOptions::TrivialAutoVarInitKind::Uninitialized + : getContext().getLangOpts().getTrivialAutoVarInit())); + + auto initializeWhatIsTechnicallyUninitialized = [&](Address addr) { + if (trivialAutoVarInit == + LangOptions::TrivialAutoVarInitKind::Uninitialized) + return; + + cgm.errorNYI(d.getSourceRange(), "emitAutoVarInit: trivial initialization"); + }; + + if (isTrivialInitializer(init)) { + initializeWhatIsTechnicallyUninitialized(addr); + return; + } + + mlir::Attribute constant; + if (emission.IsConstantAggregate || + d.mightBeUsableInConstantExpressions(getContext())) { + // FIXME: Differently from LLVM we try not to emit / lower too much + // here for CIR since we are interested in seeing the ctor in some + // analysis later on. So CIR's implementation of ConstantEmitter will + // frequently return an empty Attribute, to signal we want to codegen + // some trivial ctor calls and whatnots. + constant = ConstantEmitter(*this).tryEmitAbstractForInitializer(d); + if (constant && !mlir::isa<cir::ZeroAttr>(constant) && + (trivialAutoVarInit != + LangOptions::TrivialAutoVarInitKind::Uninitialized)) { + cgm.errorNYI(d.getSourceRange(), "emitAutoVarInit: constant aggregate"); + return; + } + } + + // NOTE(cir): In case we have a constant initializer, we can just emit a + // store. But, in CIR, we wish to retain any ctor calls, so if it is a + // CXX temporary object creation, we ensure the ctor call is used deferring + // its removal/optimization to the CIR lowering. + if (!constant || isa<CXXTemporaryObjectExpr>(init)) { + initializeWhatIsTechnicallyUninitialized(addr); + LValue lv = LValue::makeAddr(addr, type); + emitExprAsInit(init, &d, lv); + // In case lv has uses it means we indeed initialized something + // out of it while trying to build the expression, mark it as such. + mlir::Value val = lv.getAddress().getPointer(); + assert(val && "Should have an address"); + auto allocaOp = dyn_cast_or_null<cir::AllocaOp>(val.getDefiningOp()); + assert(allocaOp && "Address should come straight out of the alloca"); + + if (!allocaOp.use_empty()) + allocaOp.setInitAttr(mlir::UnitAttr::get(&getMLIRContext())); + return; + } + + // FIXME(cir): migrate most of this file to use mlir::TypedAttr directly. + auto typedConstant = mlir::dyn_cast<mlir::TypedAttr>(constant); + assert(typedConstant && "expected typed attribute"); + if (!emission.IsConstantAggregate) { + // For simple scalar/complex initialization, store the value directly. + LValue lv = LValue::makeAddr(addr, type); + assert(init && "expected initializer"); + mlir::Location initLoc = getLoc(init->getSourceRange()); + // lv.setNonGC(true); + return emitStoreThroughLValue( + RValue::get(builder.getConstant(initLoc, typedConstant)), lv); } } -void CIRGenFunction::emitAutoVarCleanups(const clang::VarDecl &d) { +void CIRGenFunction::emitAutoVarCleanups( + const CIRGenFunction::AutoVarEmission &emission) { + const VarDecl &d = *emission.Variable; + // Check the type for a cleanup. if (d.needsDestruction(getContext())) cgm.errorNYI(d.getSourceRange(), "emitAutoVarCleanups: type cleanup"); @@ -76,9 +185,9 @@ void CIRGenFunction::emitAutoVarCleanups(const clang::VarDecl &d) { /// register, or no storage class specifier. These turn into simple stack /// objects, globals depending on target. void CIRGenFunction::emitAutoVarDecl(const VarDecl &d) { - emitAutoVarAlloca(d); - emitAutoVarInit(d); - emitAutoVarCleanups(d); + CIRGenFunction::AutoVarEmission emission = emitAutoVarAlloca(d); + emitAutoVarInit(emission); + emitAutoVarCleanups(emission); } void CIRGenFunction::emitVarDecl(const VarDecl &d) { @@ -94,10 +203,59 @@ void CIRGenFunction::emitVarDecl(const VarDecl &d) { assert(d.hasLocalStorage()); - assert(!cir::MissingFeatures::opAllocaVarDeclContext()); + CIRGenFunction::VarDeclContext varDeclCtx{*this, &d}; return emitAutoVarDecl(d); } +void CIRGenFunction::emitScalarInit(const Expr *init, mlir::Location loc, + LValue lvalue, bool capturedByInit) { + Qualifiers::ObjCLifetime lifetime = Qualifiers::ObjCLifetime::OCL_None; + assert(!cir::MissingFeatures::objCLifetime()); + + if (!lifetime) { + SourceLocRAIIObject locRAII{*this, loc}; + mlir::Value value = emitScalarExpr(init); + if (capturedByInit) { + cgm.errorNYI(init->getSourceRange(), "emitScalarInit: captured by init"); + return; + } + assert(!cir::MissingFeatures::emitNullabilityCheck()); + emitStoreThroughLValue(RValue::get(value), lvalue, true); + return; + } + + cgm.errorNYI(loc, "emitScalarInit: ObjCLifetime"); +} + +void CIRGenFunction::emitExprAsInit(const Expr *init, const ValueDecl *d, + LValue lvalue, bool capturedByInit) { + SourceLocRAIIObject loc{*this, getLoc(init->getSourceRange())}; + if (capturedByInit) { + cgm.errorNYI(init->getSourceRange(), "emitExprAsInit: captured by init"); + return; + } + + QualType type = d->getType(); + + if (type->isReferenceType()) { + cgm.errorNYI(init->getSourceRange(), "emitExprAsInit: reference type"); + return; + } + switch (CIRGenFunction::getEvaluationKind(type)) { + case cir::TEK_Scalar: + emitScalarInit(init, getLoc(d->getSourceRange()), lvalue); + return; + case cir::TEK_Complex: { + cgm.errorNYI(init->getSourceRange(), "emitExprAsInit: complex type"); + return; + } + case cir::TEK_Aggregate: + cgm.errorNYI(init->getSourceRange(), "emitExprAsInit: aggregate type"); + return; + } + llvm_unreachable("bad evaluation kind"); +} + void CIRGenFunction::emitDecl(const Decl &d) { switch (d.getKind()) { case Decl::Var: { diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp index ccc3e20875263..07fb4cf8f1513 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -25,9 +25,77 @@ using namespace clang; using namespace clang::CIRGen; using namespace cir; +void CIRGenFunction::emitStoreThroughLValue(RValue src, LValue dst, + bool isInit) { + if (!dst.isSimple()) { + cgm.errorNYI(dst.getPointer().getLoc(), + "emitStoreThroughLValue: non-simple lvalue"); + return; + } + + assert(!cir::MissingFeatures::opLoadStoreObjC()); + + assert(src.isScalar() && "Can't emit an aggregate store with this method"); + emitStoreOfScalar(src.getScalarVal(), dst, isInit); +} + +void CIRGenFunction::emitStoreOfScalar(mlir::Value value, Address addr, + bool isVolatile, QualType ty, + bool isInit, bool isNontemporal) { + assert(!cir::MissingFeatures::opLoadStoreThreadLocal()); + + if (ty->getAs<clang::VectorType>()) { + cgm.errorNYI(addr.getPointer().getLoc(), "emitStoreOfScalar vector type"); + return; + } + + value = emitToMemory(value, ty); + + assert(!cir::MissingFeatures::opLoadStoreAtomic()); + + // Update the alloca with more info on initialization. + assert(addr.getPointer() && "expected pointer to exist"); + auto srcAlloca = + dyn_cast_or_null<cir::AllocaOp>(addr.getPointer().getDefiningOp()); + if (currVarDecl && srcAlloca) { + const VarDecl *vd = currVarDecl; + assert(vd && "VarDecl expected"); + if (vd->hasInit()) + srcAlloca.setInitAttr(mlir::UnitAttr::get(&getMLIRContext())); + } + + assert(currSrcLoc && "must pass in source location"); + builder.createStore(*currSrcLoc, value, addr.getPointer() /*, isVolatile*/); + + if (isNontemporal) { + cgm.errorNYI(addr.getPointer().getLoc(), "emitStoreOfScalar nontemporal"); + return; + } + + assert(!cir::MissingFeatures::opTBAA()); +} + +mlir::Value CIRGenFunction::emitToMemory(mlir::Value value, QualType ty) { + // Bool has a different representation in memory than in registers, + // but in ClangIR, it is simply represented as a cir.bool value. + // This function is here as a placeholder for possible future changes. + return value; +} + +void CIRGenFunction::emitStoreOfScalar(mlir::Value value, LValue lvalue, + bool isInit) { + if (lvalue.getType()->isConstantMatrixType()) { + assert(0 && "NYI: emitStoreOfScalar constant matrix type"); + return; + } + + emitStoreOfScalar(value, lvalue.getAddress(), lvalue.isVolatile(), + lvalue.getType(), isInit, /*isNontemporal=*/false); +} + mlir::Value CIRGenFunction::emitLoadOfScalar(LValue lvalue, SourceLocation loc) { - assert(!cir::MissingFeatures::opLoadThreadLocal()); + assert(!cir::MissingFeatures::opLoadStoreThreadLocal()); assert(!cir::MissingFeatures::opLoadEmitScalarRangeCheck()); assert(!cir::MissingFeatures::opLoadBooleanRepresentation()); @@ -115,7 +183,7 @@ mlir::Value CIRGenFunction::emitAlloca(StringRef name, mlir::Type ty, builder.restoreInsertionPoint(builder.getBestAllocaInsertPoint(entryBlock)); addr = builder.createAlloca(loc, /*addr type*/ localVarPtrTy, /*var type*/ ty, name, alignIntAttr); - assert(!cir::MissingFeatures::opAllocaVarDeclContext()); + assert(!cir::MissingFeatures::astVarDeclInterface()); } return addr; } diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp new file mode 100644 index 0000000000000..ec97cb11ccf66 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -0,0 +1,322 @@ +//===----------------------------------------------------------------------===// +// +// 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 Constant Expr nodes as LLVM code. +// +//===----------------------------------------------------------------------===// + +#include "Address.h" +#include "CIRGenConstantEmitter.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributeInterfaces.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "clang/AST/APValue.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/OperationKinds.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/Builtins.h" +#include "clang/Basic/Specifiers.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Sequence.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace clang; +using namespace clang::CIRGen; + +//===----------------------------------------------------------------------===// +// ConstExprEmitter +//===----------------------------------------------------------------------===// + +// This class only needs to handle arrays, structs and unions. +// +// In LLVM codegen, when outside C++11 mode, those types are not constant +// folded, while all other types are handled by constant folding. +// +// In CIR codegen, instead of folding things here, we should defer that work +// to MLIR: do not attempt to do much here. +class ConstExprEmitter + : public StmtVisitor<ConstExprEmitter, mlir::Attribute, QualType> { + CIRGenModule &cgm; + LLVM_ATTRIBUTE_UNUSED ConstantEmitter &emitter; + +public: + ConstExprEmitter(ConstantEmitter &emitter) + : cgm(emitter.cgm), emitter(emitter) {} + + //===--------------------------------------------------------------------===// + // Visitor Methods + //===--------------------------------------------------------------------===// + + mlir::Attribute VisitStmt(Stmt *S, QualType T) { return {}; } + + mlir::Attribute VisitConstantExpr(ConstantExpr *ce, QualType t) { + if (mlir::Attribute result = emitter.tryEmitConstantExpr(ce)) + return result; + return Visit(ce->getSubExpr(), t); + } + + mlir::Attribute VisitParenExpr(ParenExpr *pe, QualType t) { + return Visit(pe->getSubExpr(), t); + } + + mlir::Attribute + VisitSubstNonTypeTemplateParmExpr(SubstNonTypeTemplateParmExpr *pe, + QualType t) { + return Visit(pe->getReplacement(), t); + } + + mlir::Attribute VisitGenericSelectionExpr(GenericSelectionExpr *ge, + QualType t) { + return Visit(ge->getResultExpr(), t); + } + + mlir::Attribute VisitChooseExpr(ChooseExpr *ce, QualType t) { + return Visit(ce->getChosenSubExpr(), t); + } + + mlir::Attribute VisitCompoundLiteralExpr(CompoundLiteralExpr *e, QualType t) { + return Visit(e->getInitializer(), t); + } + + mlir::Attribute VisitCastExpr(CastExpr *e, QualType destType) { + cgm.errorNYI(e->getBeginLoc(), "ConstExprEmitter::VisitCastExpr"); + return {}; + } + + mlir::Attribute VisitCXXDefaultInitExpr(CXXDefaultInitExpr *die, QualType t) { + cgm.errorNYI(die->getBeginLoc(), + "ConstExprEmitter::VisitCXXDefaultInitExpr"); + return {}; + } + + mlir::Attribute VisitExprWithCleanups(ExprWithCleanups *e, QualType t) { + // Since this about constant emission no need to wrap this under a scope. + return Visit(e->getSubExpr(), t); + } + + mlir::Attribute VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *e, + QualType t) { + return Visit(e->getSubExpr(), t); + } + + mlir::Attribute VisitImplicitValueInitExpr(ImplicitValueInitExpr *E, + QualType T) { + cgm.errorNYI(E->getBeginLoc(), + "ConstExprEmitter::VisitImplicitValueInitExpr"); + return {}; + } + + mlir::Attribute VisitInitListExpr(InitListExpr *ile, QualType t) { + cgm.errorNYI(ile->getBeginLoc(), "ConstExprEmitter::VisitInitListExpr"); + return {}; + } + + mlir::Attribute VisitDesignatedInitUpdateExpr(DesignatedInitUpdateExpr *e, + QualType destType) { + mlir::Attribute c = Visit(e->getBase(), destType); + if (!c) + return {}; + + cgm.errorNYI(e->getBeginLoc(), + "ConstExprEmitter::VisitDesignatedInitUpdateExpr"); + return {}; + } + + mlir::Attribute VisitCXXConstructExpr(CXXConstructExpr *e, QualType ty) { + cgm.errorNYI(e->getBeginLoc(), "ConstExprEmitter::VisitCXXConstructExpr"); + return {}; + } + + mlir::Attribute VisitStringLiteral(StringLiteral *e, QualType t) { + cgm.errorNYI(e->getBeginLoc(), "ConstExprEmitter::VisitStringLiteral"); + return {}; + } + + mlir::Attribute VisitObjCEncodeExpr(ObjCEncodeExpr *e, QualType t) { + cgm.errorNYI(e->getBeginLoc(), "ConstExprEmitter::VisitObjCEncodeExpr"); + return {}; + } + + mlir::Attribute VisitUnaryExtension(const UnaryOperator *e, QualType t) { + return Visit(e->getSubExpr(), t); + } + + // Utility methods + mlir::Type convertType(QualType t) { return cgm.convertType(t); } +}; + +// TODO(cir): this can be shared with LLVM's codegen +static QualType getNonMemoryType(CIRGenModule &cgm, QualType type) { + if (auto at = type->getAs<AtomicType>()) { + return cgm.getASTContext().getQualifiedType(at->getValueType(), + type.getQualifiers()); + } + return type; +} + +//===----------------------------------------------------------------------===// +// ConstantEmitter +//===----------------------------------------------------------------------===// + +mlir::Attribute ConstantEmitter::validateAndPopAbstract(mlir::Attribute c, + AbstractState saved) { + abstract = saved.oldValue; + + // No validation necessary for now. + // No cleanup to do for now. + return c; +} + +mlir::Attribute +ConstantEmitter::tryEmitAbstractForInitializer(const VarDecl &d) { + AbstractState state = pushAbstract(); + mlir::Attribute c = tryEmitPrivateForVarInit(d); + return validateAndPopAbstract(c, state); +} + +mlir::Attribute ConstantEmitter::tryEmitPrivateForVarInit(const VarDecl &d) { + // Make a quick check if variable can be default NULL initialized + // and avoid going through rest of code which may do, for c++11, + // initialization of memory to all NULLs. + if (!d.hasLocalStorage()) { + QualType ty = cgm.getASTContext().getBaseElementType(d.getType()); + if (ty->isRecordType()) + if (d.getInit() && isa<CXXConstructExpr>(d.getInit())) { + cgm.errorNYI(d.getInit()->getBeginLoc(), + "tryEmitPrivateForVarInit CXXConstructExpr"); + return {}; + } + } + inConstantContext = d.hasConstantInitialization(); + + const Expr *e = d.getInit(); + assert(e && "No initializer to emit"); + + QualType destType = d.getType(); + + if (!destType->isReferenceType()) { + QualType nonMemoryDestType = getNonMemoryType(cgm, destType); + if (mlir::Attribute c = ConstExprEmitter(*this).Visit(const_cast<Expr *>(e), + nonMemoryDestType)) + return emitForMemory(c, destType); + } + + // Try to emit the initializer. Note that this can allow some things that + // are not allowed by tryEmitPrivateForMemory alone. + if (APValue *value = d.evaluateValue()) + return tryEmitPrivateForMemory(*value, destType); + + return {}; +} + +mlir::Attribute ConstantEmitter::tryEmitConstantExpr(const ConstantExpr *ce) { + if (!ce->hasAPValueResult()) + return {}; + + QualType retType = ce->getType(); + if (ce->isGLValue()) + retType = cgm.getASTContext().getLValueReferenceType(retType); + + return emitAbstract(ce->getBeginLoc(), ce->getAPValueResult(), retType); +} + +mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const APValue &value, + QualType destType) { + QualType nonMemoryDestType = getNonMemoryType(cgm, destType); + mlir::Attribute c = tryEmitPrivate(value, nonMemoryDestType); + return (c ? emitForMemory(c, destType) : nullptr); +} + +mlir::Attribute ConstantEmitter::emitAbstract(SourceLocation loc, + const APValue &value, + QualType destType) { + AbstractState state = pushAbstract(); + mlir::Attribute c = tryEmitPrivate(value, destType); + c = validateAndPopAbstract(c, state); + if (!c) + cgm.errorNYI(loc, "emitAbstract failed"); + return c; +} + +mlir::Attribute ConstantEmitter::emitForMemory(mlir::Attribute c, + QualType destType) { + // For an _Atomic-qualified constant, we may need to add tail padding. + if (destType->getAs<AtomicType>()) { + cgm.errorNYI("emitForMemory: atomic type"); + return {}; + } + + return c; +} + +mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value, + QualType destType) { + auto &builder = cgm.getBuilder(); + switch (value.getKind()) { + case APValue::None: + case APValue::Indeterminate: + // TODO(cir): LLVM models out-of-lifetime and indeterminate values as + // 'undef'. Find out what's better for CIR. + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate none or indeterminate"); + return {}; + case APValue::Int: { + mlir::Type ty = cgm.convertType(destType); + if (mlir::isa<cir::BoolType>(ty)) + return builder.getCIRBoolAttr(value.getInt().getZExtValue()); + assert(mlir::isa<cir::IntType>(ty) && "expected integral type"); + return cgm.getBuilder().getAttr<cir::IntAttr>(ty, value.getInt()); + } + case APValue::Float: { + const llvm::APFloat &init = value.getFloat(); + if (&init.getSemantics() == &llvm::APFloat::IEEEhalf() && + !cgm.getASTContext().getLangOpts().NativeHalfType && + cgm.getASTContext().getTargetInfo().useFP16ConversionIntrinsics()) { + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate half"); + return {}; + } else { + mlir::Type ty = cgm.convertType(destType); + assert(mlir::isa<cir::CIRFPTypeInterface>(ty) && + "expected floating-point type"); + return cgm.getBuilder().getAttr<cir::FPAttr>(ty, init); + } + } + case APValue::Array: { + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate array"); + return {}; + } + case APValue::Vector: { + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate vector"); + return {}; + } + case APValue::MemberPointer: { + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate member pointer"); + return {}; + } + case APValue::LValue: + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate lvalue"); + return {}; + case APValue::Struct: + case APValue::Union: + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate struct or union"); + return {}; + case APValue::FixedPoint: + case APValue::ComplexInt: + case APValue::ComplexFloat: + case APValue::AddrLabelDiff: + cgm.errorNYI("ConstExprEmitter::tryEmitPrivate fixed point, complex int, " + "complex float, addr label diff"); + return {}; + } + llvm_unreachable("Unknown APValue kind"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index cf896d3c0a946..72445f62232a4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -26,6 +26,7 @@ #include "clang/AST/Decl.h" #include "clang/AST/Type.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/MissingFeatures.h" #include "clang/CIR/TypeEvaluationKind.h" #include "llvm/ADT/ScopedHashTable.h" @@ -105,6 +106,27 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::Value emitAlloca(llvm::StringRef name, mlir::Type ty, mlir::Location loc, clang::CharUnits alignment); +private: + // Track current variable initialization (if there's one) + const clang::VarDecl *currVarDecl = nullptr; + class VarDeclContext { + CIRGenFunction &p; + const clang::VarDecl *oldVal = nullptr; + + public: + VarDeclContext(CIRGenFunction &p, const VarDecl *value) : p(p) { + if (p.currVarDecl) + oldVal = p.currVarDecl; + p.currVarDecl = value; + } + + /// Can be used to restore the state early, before the dtor + /// is run. + void restore() { p.currVarDecl = oldVal; } + ~VarDeclContext() { restore(); } + }; + +public: /// Use to track source locations across nested visitor traversals. /// Always use a `SourceLocRAIIObject` to change currSrcLoc. std::optional<mlir::Location> currSrcLoc; @@ -170,16 +192,97 @@ class CIRGenFunction : public CIRGenTypeCache { void emitDecl(const clang::Decl &d); + void emitScalarInit(const clang::Expr *init, mlir::Location loc, + LValue lvalue, bool capturedByInit = false); + LValue emitDeclRefLValue(const clang::DeclRefExpr *e); + /// Determine whether the given initializer is trivial in the sense + /// that it requires no code to be generated. + bool isTrivialInitializer(const Expr *init); + + /// Emit an expression as an initializer for an object (variable, field, etc.) + /// at the given location. The expression is not necessarily the normal + /// initializer for the object, and the address is not necessarily + /// its normal location. + /// + /// \param init the initializing expression + /// \param d the object to act as if we're initializing + /// \param lvalue the lvalue to initialize + /// \param capturedByInit true if \p d is a __block variable whose address is + /// potentially changed by the initializer + void emitExprAsInit(const clang::Expr *init, const clang::ValueDecl *d, + LValue lvalue, bool capturedByInit = false); + /// Emit code and set up symbol table for a variable declaration with auto, /// register, or no storage class specifier. These turn into simple stack /// objects, globals depending on target. void emitAutoVarDecl(const clang::VarDecl &d); - void emitAutoVarAlloca(const clang::VarDecl &d); - void emitAutoVarInit(const clang::VarDecl &d); - void emitAutoVarCleanups(const clang::VarDecl &d); + struct AutoVarEmission { + const clang::VarDecl *Variable; + /// The address of the alloca for languages with explicit address space + /// (e.g. OpenCL) or alloca casted to generic pointer for address space + /// agnostic languages (e.g. C++). Invalid if the variable was emitted + /// as a global constant. + Address Addr; + + /// True if the variable is of aggregate type and has a constant + /// initializer. + bool IsConstantAggregate = false; + + /// True if the variable is a __block variable that is captured by an + /// escaping block. + bool IsEscapingByRef = false; + + mlir::Value NRVOFlag{}; + + struct Invalid {}; + AutoVarEmission(Invalid) : Variable(nullptr), Addr(Address::invalid()) {} + + AutoVarEmission(const clang::VarDecl &variable) + : Variable(&variable), Addr(Address::invalid()) {} + + static AutoVarEmission invalid() { return AutoVarEmission(Invalid()); } + + bool wasEmittedAsGlobal() const { return !Addr.isValid(); } + + /// Returns the raw, allocated address, which is not necessarily + /// the address of the object itself. It is casted to default + /// address space for address space agnostic languages. + Address getAllocatedAddress() const { return Addr; } + + /// Returns the address of the object within this declaration. + /// Note that this does not chase the forwarding pointer for + /// __block decls. + Address getObjectAddress(CIRGenFunction &CGF) const { + if (!IsEscapingByRef) + return Addr; + + assert(!cir::MissingFeatures::opAllocaEscapeByReference()); + return Address::invalid(); + } + }; + + AutoVarEmission emitAutoVarAlloca(const clang::VarDecl &d); + void emitAutoVarInit(const AutoVarEmission &emission); + void emitAutoVarCleanups(const AutoVarEmission &emission); + + void emitStoreOfScalar(mlir::Value value, Address addr, bool isVolatile, + clang::QualType ty, bool isInit = false, + bool isNontemporal = false); + void emitStoreOfScalar(mlir::Value value, LValue lvalue, bool isInit); + + /// Given a value and its clang type, returns the value casted to its memory + /// representation. + /// Note: CIR defers most of the special casting to the final lowering passes + /// to conserve the high level information. + mlir::Value emitToMemory(mlir::Value Value, clang::QualType Ty); + + /// Store the specified rvalue into the specified + /// lvalue, where both are guaranteed to the have the same type, and that type + /// is 'Ty'. + void emitStoreThroughLValue(RValue Src, LValue Dst, bool isInit = false); /// This method handles emission of any variable declaration /// inside a function, including static vars etc. diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 5fb5ef505a8c1..2a798f4cd56a9 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -147,6 +147,20 @@ class CIRGenModule : public CIRGenTypeCache { return diags.Report(loc, diagID) << feature << name; } + DiagnosticBuilder errorNYI(mlir::Location loc, llvm::StringRef feature) { + // TODO: Convert the location to a SourceLocation + unsigned diagID = diags.getCustomDiagID( + DiagnosticsEngine::Error, "ClangIR code gen Not Yet Implemented: %0"); + return diags.Report(diagID) << feature; + } + + DiagnosticBuilder errorNYI(llvm::StringRef feature) { + // TODO: Make a default location? currSrcLoc? + unsigned diagID = diags.getCustomDiagID( + DiagnosticsEngine::Error, "ClangIR code gen Not Yet Implemented: %0"); + return diags.Report(diagID) << feature; + } + DiagnosticBuilder errorNYI(SourceRange, llvm::StringRef); template <typename T> diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt index e6d3cbabd853b..8ee65c2763e70 100644 --- a/clang/lib/CIR/CodeGen/CMakeLists.txt +++ b/clang/lib/CIR/CodeGen/CMakeLists.txt @@ -10,6 +10,7 @@ add_clang_library(clangCIR CIRGenerator.cpp CIRGenDecl.cpp CIRGenExpr.cpp + CIRGenExprConstant.cpp CIRGenExprScalar.cpp CIRGenFunction.cpp CIRGenModule.cpp diff --git a/clang/test/CIR/CodeGen/basic.cpp b/clang/test/CIR/CodeGen/basic.cpp index 6a2faa725a34d..ef922cc2b46fc 100644 --- a/clang/test/CIR/CodeGen/basic.cpp +++ b/clang/test/CIR/CodeGen/basic.cpp @@ -1,9 +1,4 @@ -// RUN: not %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s - -// This error is caused by the "const int i = 2" line in f2(). When -// initaliziers are implemented, the checks there should be updated -// and the "not" should be removed from the run line. -// CHECK: error: ClangIR code gen Not Yet Implemented: emitAutoVarInit +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s int f1() { int i; @@ -22,13 +17,15 @@ int f2() { } // CHECK: cir.func @f2() -> !cir.int<s, 32> -// CHECK: %[[I_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i", const] {alignment = 4 : i64} +// CHECK: %[[I_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i", init, const] {alignment = 4 : i64} +// CHECK: %[[TWO:.*]] = cir.const #cir.int<2> : !cir.int<s, 32> +// CHECK: cir.store %[[TWO]], %[[I_PTR]] : !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>> // CHECK: %[[I:.*]] = cir.load %[[I_PTR]] : !cir.ptr<!cir.int<s, 32>>, !cir.int<s, 32> // CHECK: cir.return %[[I]] : !cir.int<s, 32> int f3(int i) { - return i; - } + return i; +} // CHECK: cir.func @f3(%[[ARG:.*]]: !cir.int<s, 32> loc({{.*}})) -> !cir.int<s, 32> // CHECK: %[[ARG_ALLOCA:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i", init] {alignment = 4 : i64} diff --git a/clang/test/CIR/CodeGen/local-vars.cpp b/clang/test/CIR/CodeGen/local-vars.cpp new file mode 100644 index 0000000000000..14be8f4da902b --- /dev/null +++ b/clang/test/CIR/CodeGen/local-vars.cpp @@ -0,0 +1,65 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s + +void test() { + int i = 1; + long l = 2l; + float f = 3.0f; + double d = 4.0; + bool b1 = true; + bool b2 = false; + const int ci = 1; + const long cl = 2l; + const float cf = 3.0f; + const double cd = 4.0; + const bool cb1 = true; + const bool cb2 = false; + int uii; + long uil; + float uif; + double uid; + bool uib; +} + +// CHECK: module +// CHECK: cir.func @test() +// CHECK: %[[I_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i", init] {alignment = 4 : i64} +// CHECK: %[[L_PTR:.*]] = cir.alloca !cir.int<s, 64>, !cir.ptr<!cir.int<s, 64>>, ["l", init] {alignment = 8 : i64} +// CHECK: %[[F_PTR:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["f", init] {alignment = 4 : i64} +// CHECK: %[[D_PTR:.*]] = cir.alloca !cir.double, !cir.ptr<!cir.double>, ["d", init] {alignment = 8 : i64} +// CHECK: %[[B1_PTR:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["b1", init] {alignment = 1 : i64} +// CHECK: %[[B2_PTR:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["b2", init] {alignment = 1 : i64} +// CHECK: %[[CI_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["ci", init, const] {alignment = 4 : i64} +// CHECK: %[[CL_PTR:.*]] = cir.alloca !cir.int<s, 64>, !cir.ptr<!cir.int<s, 64>>, ["cl", init, const] {alignment = 8 : i64} +// CHECK: %[[CF_PTR:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["cf", init, const] {alignment = 4 : i64} +// CHECK: %[[CD_PTR:.*]] = cir.alloca !cir.double, !cir.ptr<!cir.double>, ["cd", init, const] {alignment = 8 : i64} +// CHECK: %[[CB1_PTR:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["cb1", init, const] {alignment = 1 : i64} +// CHECK: %[[CB2_PTR:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["cb2", init, const] {alignment = 1 : i64} +// CHECK: %[[UII_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["uii"] {alignment = 4 : i64} +// CHECK: %[[UIL_PTR:.*]] = cir.alloca !cir.int<s, 64>, !cir.ptr<!cir.int<s, 64>>, ["uil"] {alignment = 8 : i64} +// CHECK: %[[UIF_PTR:.*]] = cir.alloca !cir.float, !cir.ptr<!cir.float>, ["uif"] {alignment = 4 : i64} +// CHECK: %[[UID_PTR:.*]] = cir.alloca !cir.double, !cir.ptr<!cir.double>, ["uid"] {alignment = 8 : i64} +// CHECK: %[[UIB_PTR:.*]] = cir.alloca !cir.bool, !cir.ptr<!cir.bool>, ["uib"] {alignment = 1 : i64} +// CHECK: %[[ONE:.*]] = cir.const #cir.int<1> : !cir.int<s, 32> +// CHECK: cir.store %[[ONE]], %[[I_PTR]] : !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>> +// CHECK: %[[TWO:.*]] = cir.const #cir.int<2> : !cir.int<s, 64> +// CHECK: cir.store %[[TWO]], %[[L_PTR]] : !cir.int<s, 64>, !cir.ptr<!cir.int<s, 64>> +// CHECK: %[[THREE:.*]] = cir.const #cir.fp<3.0{{.*}}> : !cir.float +// CHECK: cir.store %[[THREE]], %[[F_PTR]] : !cir.float, !cir.ptr<!cir.float> +// CHECK: %[[FOUR:.*]] = cir.const #cir.fp<4.0{{.*}}> : !cir.double +// CHECK: cir.store %[[FOUR]], %[[D_PTR]] : !cir.double, !cir.ptr<!cir.double> +// CHECK: %[[TRUE:.*]] = cir.const #true +// CHECK: cir.store %[[TRUE]], %[[B1_PTR]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[FALSE:.*]] = cir.const #false +// CHECK: cir.store %[[FALSE]], %[[B2_PTR]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[ONEC:.*]] = cir.const #cir.int<1> : !cir.int<s, 32> +// CHECK: cir.store %[[ONEC]], %[[CI_PTR]] : !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>> +// CHECK: %[[TWOC:.*]] = cir.const #cir.int<2> : !cir.int<s, 64> +// CHECK: cir.store %[[TWOC]], %[[CL_PTR]] : !cir.int<s, 64>, !cir.ptr<!cir.int<s, 64>> +// CHECK: %[[THREEC:.*]] = cir.const #cir.fp<3.0{{.*}}> : !cir.float +// CHECK: cir.store %[[THREEC]], %[[CF_PTR]] : !cir.float, !cir.ptr<!cir.float> +// CHECK: %[[FOURC:.*]] = cir.const #cir.fp<4.0{{.*}}> : !cir.double +// CHECK: cir.store %[[FOURC]], %[[CD_PTR]] : !cir.double, !cir.ptr<!cir.double> +// CHECK: %[[TRUEC:.*]] = cir.const #true +// CHECK: cir.store %[[TRUEC]], %[[CB1_PTR]] : !cir.bool, !cir.ptr<!cir.bool> +// CHECK: %[[FALSEC:.*]] = cir.const #false +// CHECK: cir.store %[[FALSEC]], %[[CB2_PTR]] : !cir.bool, !cir.ptr<!cir.bool> diff --git a/clang/test/CIR/Lowering/basic.cpp b/clang/test/CIR/Lowering/basic.cpp index 2c29368bd5835..d1dc343a068a8 100644 --- a/clang/test/CIR/Lowering/basic.cpp +++ b/clang/test/CIR/Lowering/basic.cpp @@ -1,9 +1,4 @@ -// RUN: not %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - 2>&1 | FileCheck %s - -// This error is caused by the "const int i = 2" line in f2(). When -// initaliziers are implemented, the checks there should be updated -// and the "not" should be removed from the run line. -// CHECK: error: ClangIR code gen Not Yet Implemented: emitAutoVarInit +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - 2>&1 | FileCheck %s int f1() { int i; @@ -22,6 +17,7 @@ int f2() { // CHECK: define{{.*}} i32 @f2() { // CHECK: %[[I_PTR:.*]] = alloca i32, i64 1, align 4 +// CHECK: store i32 2, ptr %[[I_PTR]], align 4 // CHECK: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4 // CHECK: ret i32 %[[I]] diff --git a/clang/test/CIR/Lowering/local-vars.cpp b/clang/test/CIR/Lowering/local-vars.cpp new file mode 100644 index 0000000000000..bd47ed14065c6 --- /dev/null +++ b/clang/test/CIR/Lowering/local-vars.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o - 2>&1 | FileCheck %s + +void test() { + int i = 1; + long l = 2l; + float f = 3.0f; + double d = 4.0; + bool b1 = true; + bool b2 = false; + const int ci = 1; + const long cl = 2l; + const float cf = 3.0f; + const double cd = 4.0; + const bool cb1 = true; + const bool cb2 = false; + int uii; + long uil; + float uif; + double uid; + bool uib; +} + +// Note: The alignment of i64 stores below is wrong. That should be fixed +// when we add alignment attributes to the load/store ops. + +// CHECK: define{{.*}} void @test() +// CHECK: %[[I_PTR:.*]] = alloca i32, i64 1, align 4 +// CHECK: %[[L_PTR:.*]] = alloca i64, i64 1, align 8 +// CHECK: %[[F_PTR:.*]] = alloca float, i64 1, align 4 +// CHECK: %[[D_PTR:.*]] = alloca double, i64 1, align 8 +// CHECK: %[[B1_PTR:.*]] = alloca i8, i64 1, align 1 +// CHECK: %[[B2_PTR:.*]] = alloca i8, i64 1, align 1 +// CHECK: %[[CI_PTR:.*]] = alloca i32, i64 1, align 4 +// CHECK: %[[CL_PTR:.*]] = alloca i64, i64 1, align 8 +// CHECK: %[[CF_PTR:.*]] = alloca float, i64 1, align 4 +// CHECK: %[[CD_PTR:.*]] = alloca double, i64 1, align 8 +// CHECK: %[[CB1_PTR:.*]] = alloca i8, i64 1, align 1 +// CHECK: %[[CB2_PTR:.*]] = alloca i8, i64 1, align 1 +// CHECK: %[[UII_PTR:.*]] = alloca i32, i64 1, align 4 +// CHECK: %[[UIL_PTR:.*]] = alloca i64, i64 1, align 8 +// CHECK: %[[UIF_PTR:.*]] = alloca float, i64 1, align 4 +// CHECK: %[[UID_PTR:.*]] = alloca double, i64 1, align 8 +// CHECK: %[[UIB_PTR:.*]] = alloca i8, i64 1, align 1 +// CHECK: store i32 1, ptr %[[I_PTR]], align 4 +// CHECK: store i64 2, ptr %[[L_PTR]], align 4 +// CHECK: store float 3.000000e+00, ptr %[[F_PTR]], align 4 +// CHECK: store double 4.000000e+00, ptr %[[D_PTR]], align 8 +// CHECK: store i8 1, ptr %[[B1_PTR]], align 1 +// CHECK: store i8 0, ptr %[[B2_PTR]], align 1 +// CHECK: store i32 1, ptr %[[CI_PTR]], align 4 +// CHECK: store i64 2, ptr %[[CL_PTR]], align 4 +// CHECK: store float 3.000000e+00, ptr %[[CF_PTR]], align 4 +// CHECK: store double 4.000000e+00, ptr %[[CD_PTR]], align 8 +// CHECK: store i8 1, ptr %[[CB1_PTR]], align 1 +// CHECK: store i8 0, ptr %[[CB2_PTR]], align 1 >From 5ad8b1a717ff7ff277f7fac0c5605871ce67a612 Mon Sep 17 00:00:00 2001 From: Andy Kaylor <akay...@nvidia.com> Date: Thu, 6 Mar 2025 13:18:39 -0800 Subject: [PATCH 2/2] Address review feedback --- clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h | 16 ++++++------ clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 25 ++++++++++--------- clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp | 21 +++------------- 3 files changed, 26 insertions(+), 36 deletions(-) diff --git a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h index e29149c2e70f1..5b22a8e59908d 100644 --- a/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h +++ b/clang/lib/CIR/CodeGen/CIRGenConstantEmitter.h @@ -82,15 +82,17 @@ class ConstantEmitter { mlir::Attribute tryEmitAbstractForInitializer(const VarDecl &d); private: - struct AbstractState { + class AbstractStateRAII { + ConstantEmitter &emitter; bool oldValue; + + public: + AbstractStateRAII(ConstantEmitter &emitter, bool value) + : emitter(emitter), oldValue(emitter.abstract) { + emitter.abstract = value; + } + ~AbstractStateRAII() { emitter.abstract = oldValue; } }; - AbstractState pushAbstract() { - AbstractState saved = {abstract}; - abstract = true; - return saved; - } - mlir::Attribute validateAndPopAbstract(mlir::Attribute C, AbstractState save); }; } // namespace clang::CIRGen diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp index ed42cd6bd2c32..27ed0113a4f55 100644 --- a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -75,6 +75,12 @@ bool CIRGenFunction::isTrivialInitializer(const Expr *init) { void CIRGenFunction::emitAutoVarInit( const CIRGenFunction::AutoVarEmission &emission) { + assert(emission.Variable && "emission was not valid!"); + + // If this was emitted as a global constant, we're done. + if (emission.wasEmittedAsGlobal()) + return; + const VarDecl &d = *emission.Variable; QualType type = d.getType(); @@ -209,22 +215,17 @@ void CIRGenFunction::emitVarDecl(const VarDecl &d) { void CIRGenFunction::emitScalarInit(const Expr *init, mlir::Location loc, LValue lvalue, bool capturedByInit) { - Qualifiers::ObjCLifetime lifetime = Qualifiers::ObjCLifetime::OCL_None; assert(!cir::MissingFeatures::objCLifetime()); - if (!lifetime) { - SourceLocRAIIObject locRAII{*this, loc}; - mlir::Value value = emitScalarExpr(init); - if (capturedByInit) { - cgm.errorNYI(init->getSourceRange(), "emitScalarInit: captured by init"); - return; - } - assert(!cir::MissingFeatures::emitNullabilityCheck()); - emitStoreThroughLValue(RValue::get(value), lvalue, true); + SourceLocRAIIObject locRAII{*this, loc}; + mlir::Value value = emitScalarExpr(init); + if (capturedByInit) { + cgm.errorNYI(init->getSourceRange(), "emitScalarInit: captured by init"); return; } - - cgm.errorNYI(loc, "emitScalarInit: ObjCLifetime"); + assert(!cir::MissingFeatures::emitNullabilityCheck()); + emitStoreThroughLValue(RValue::get(value), lvalue, true); + return; } void CIRGenFunction::emitExprAsInit(const Expr *init, const ValueDecl *d, diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp index ec97cb11ccf66..1ea7f6212766c 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprConstant.cpp @@ -169,20 +169,10 @@ static QualType getNonMemoryType(CIRGenModule &cgm, QualType type) { // ConstantEmitter //===----------------------------------------------------------------------===// -mlir::Attribute ConstantEmitter::validateAndPopAbstract(mlir::Attribute c, - AbstractState saved) { - abstract = saved.oldValue; - - // No validation necessary for now. - // No cleanup to do for now. - return c; -} - mlir::Attribute ConstantEmitter::tryEmitAbstractForInitializer(const VarDecl &d) { - AbstractState state = pushAbstract(); - mlir::Attribute c = tryEmitPrivateForVarInit(d); - return validateAndPopAbstract(c, state); + AbstractStateRAII state(*this, true); + return tryEmitPrivateForVarInit(d); } mlir::Attribute ConstantEmitter::tryEmitPrivateForVarInit(const VarDecl &d) { @@ -241,11 +231,10 @@ mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const APValue &value, mlir::Attribute ConstantEmitter::emitAbstract(SourceLocation loc, const APValue &value, QualType destType) { - AbstractState state = pushAbstract(); + AbstractStateRAII state(*this, true); mlir::Attribute c = tryEmitPrivate(value, destType); - c = validateAndPopAbstract(c, state); if (!c) - cgm.errorNYI(loc, "emitAbstract failed"); + cgm.errorNYI(loc, "emitAbstract failed, emit null constaant"); return c; } @@ -266,8 +255,6 @@ mlir::Attribute ConstantEmitter::tryEmitPrivate(const APValue &value, switch (value.getKind()) { case APValue::None: case APValue::Indeterminate: - // TODO(cir): LLVM models out-of-lifetime and indeterminate values as - // 'undef'. Find out what's better for CIR. cgm.errorNYI("ConstExprEmitter::tryEmitPrivate none or indeterminate"); return {}; case APValue::Int: { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits