Author: Andy Kaylor Date: 2025-02-27T14:22:26-08:00 New Revision: 3989b78fa96f6c93da0fa23c7aa29a313b56831d
URL: https://github.com/llvm/llvm-project/commit/3989b78fa96f6c93da0fa23c7aa29a313b56831d DIFF: https://github.com/llvm/llvm-project/commit/3989b78fa96f6c93da0fa23c7aa29a313b56831d.diff LOG: [CIR] Upstream basic alloca and load support (#128792) This change implements basic support in ClangIR for local variables using the cir.alloca and cir.load operations. Added: clang/lib/CIR/CodeGen/Address.h clang/lib/CIR/CodeGen/CIRGenDecl.cpp clang/lib/CIR/CodeGen/CIRGenExpr.cpp clang/lib/CIR/CodeGen/CIRGenValue.h clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp clang/test/CIR/CodeGen/basic.cpp Modified: clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h clang/include/clang/CIR/Dialect/IR/CIRAttrs.td clang/include/clang/CIR/Dialect/IR/CIROps.td clang/include/clang/CIR/MissingFeatures.h clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp clang/lib/CIR/CodeGen/CIRGenFunction.cpp clang/lib/CIR/CodeGen/CIRGenFunction.h clang/lib/CIR/CodeGen/CIRGenModule.h clang/lib/CIR/CodeGen/CIRGenStmt.cpp clang/lib/CIR/CodeGen/CMakeLists.txt clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/lib/CIR/Dialect/IR/CMakeLists.txt Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h index f03241a875845..14afdfc2758ea 100644 --- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h +++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h @@ -9,6 +9,7 @@ #ifndef LLVM_CLANG_CIR_DIALECT_BUILDER_CIRBASEBUILDER_H #define LLVM_CLANG_CIR_DIALECT_BUILDER_CIRBASEBUILDER_H +#include "clang/AST/CharUnits.h" #include "clang/CIR/Dialect/IR/CIRAttrs.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/Dialect/IR/CIRTypes.h" @@ -51,6 +52,47 @@ class CIRBaseBuilderTy : public mlir::OpBuilder { return cir::ConstPtrAttr::get( getContext(), mlir::cast<cir::PointerType>(type), valueAttr); } + + mlir::Value createAlloca(mlir::Location loc, cir::PointerType addrType, + mlir::Type type, llvm::StringRef name, + mlir::IntegerAttr alignment) { + return create<cir::AllocaOp>(loc, addrType, type, name, alignment); + } + + cir::LoadOp createLoad(mlir::Location loc, mlir::Value ptr, + bool isVolatile = false, uint64_t alignment = 0) { + mlir::IntegerAttr intAttr; + if (alignment) + intAttr = mlir::IntegerAttr::get( + mlir::IntegerType::get(ptr.getContext(), 64), alignment); + + return create<cir::LoadOp>(loc, ptr); + } + + // + // Block handling helpers + // ---------------------- + // + static OpBuilder::InsertPoint getBestAllocaInsertPoint(mlir::Block *block) { + auto last = + std::find_if(block->rbegin(), block->rend(), [](mlir::Operation &op) { + // TODO: Add LabelOp missing feature here + return mlir::isa<cir::AllocaOp>(&op); + }); + + if (last != block->rend()) + return OpBuilder::InsertPoint(block, ++mlir::Block::iterator(&*last)); + return OpBuilder::InsertPoint(block, block->begin()); + }; + + mlir::IntegerAttr getSizeFromCharUnits(mlir::MLIRContext *ctx, + clang::CharUnits size) { + // Note that mlir::IntegerType is used instead of cir::IntType here + // because we don't need sign information for this to be useful, so keep + // it simple. + return mlir::IntegerAttr::get(mlir::IntegerType::get(ctx, 64), + size.getQuantity()); + } }; } // namespace cir diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 097616ba06749..ece04c225e322 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -54,6 +54,21 @@ def CIR_BoolAttr : CIR_Attr<"Bool", "bool", [TypedAttrInterface]> { }]; } +//===----------------------------------------------------------------------===// +// UndefAttr +//===----------------------------------------------------------------------===// + +def UndefAttr : CIR_Attr<"Undef", "undef", [TypedAttrInterface]> { + let summary = "Represent an undef constant"; + let description = [{ + The UndefAttr represents an undef constant, corresponding to LLVM's notion + of undef. + }]; + + let parameters = (ins AttributeSelfTypeParameter<"">:$type); + let assemblyFormat = [{}]; +} + //===----------------------------------------------------------------------===// // IntegerAttr //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index f9ce38588e436..083cf46a93ae6 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -115,6 +115,119 @@ def ConstantOp : CIR_Op<"const", let hasFolder = 1; } +//===----------------------------------------------------------------------===// +// AllocaOp +//===----------------------------------------------------------------------===// + +class AllocaTypesMatchWith<string summary, string lhsArg, string rhsArg, + string transform, string comparator = "std::equal_to<>()"> + : PredOpTrait<summary, CPred< + comparator # "(" # + !subst("$_self", "$" # lhsArg # ".getType()", transform) # + ", $" # rhsArg # ")">> { + string lhs = lhsArg; + string rhs = rhsArg; + string transformer = transform; +} + +def AllocaOp : CIR_Op<"alloca", [ + AllocaTypesMatchWith<"'allocaType' matches pointee type of 'addr'", + "addr", "allocaType", + "cast<PointerType>($_self).getPointee()">, + DeclareOpInterfaceMethods<PromotableAllocationOpInterface>]> { + let summary = "Defines a scope-local variable"; + let description = [{ + The `cir.alloca` operation defines a scope-local variable. + + The presence of the `const` attribute indicates that the local variable is + declared with C/C++ `const` keyword. + + The result type is a pointer to the input's type. + + Example: + + ```mlir + // int count; + %0 = cir.alloca i32, !cir.ptr<i32>, ["count"] {alignment = 4 : i64} + + // int *ptr; + %1 = cir.alloca !cir.ptr<i32>, !cir.ptr<!cir.ptr<i32>>, ["ptr"] {alignment = 8 : i64} + ... + ``` + }]; + + let arguments = (ins + TypeAttr:$allocaType, + StrAttr:$name, + UnitAttr:$init, + UnitAttr:$constant, + ConfinedAttr<OptionalAttr<I64Attr>, [IntMinValue<0>]>:$alignment, + OptionalAttr<ArrayAttr>:$annotations + ); + + let results = (outs Res<CIR_PointerType, "", + [MemAlloc<AutomaticAllocationScopeResource>]>:$addr); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "mlir::Type":$addr, + "mlir::Type":$allocaType, + "llvm::StringRef":$name, + "mlir::IntegerAttr":$alignment)> + ]; + + let extraClassDeclaration = [{ + // Whether the alloca input type is a pointer. + bool isPointerType() { return ::mlir::isa<::cir::PointerType>(getAllocaType()); } + }]; + + let assemblyFormat = [{ + $allocaType `,` qualified(type($addr)) `,` + `[` $name + (`,` `init` $init^)? + (`,` `const` $constant^)? + `]` + ($annotations^)? attr-dict + }]; + + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// LoadOp +//===----------------------------------------------------------------------===// + +def LoadOp : CIR_Op<"load", [ + TypesMatchWith<"type of 'result' matches pointee type of 'addr'", + "addr", "result", + "cast<PointerType>($_self).getPointee()">, + DeclareOpInterfaceMethods<PromotableMemOpInterface>]> { + + let summary = "Load value from memory adddress"; + let description = [{ + `cir.load` reads a value (lvalue to rvalue conversion) given an address + backed up by a `cir.ptr` type. + + Example: + + ```mlir + + // Read from local variable, address in %0. + %1 = cir.load %0 : !cir.ptr<i32>, i32 + ``` + }]; + + let arguments = (ins Arg<CIR_PointerType, "the address to load from", + [MemRead]>:$addr); + let results = (outs CIR_AnyType:$result); + + let assemblyFormat = [{ + $addr `:` qualified(type($addr)) `,` type($result) attr-dict + }]; + + // FIXME: add verifier. +} + //===----------------------------------------------------------------------===// // ReturnOp //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index d4fcd52e7e6e3..5c7e10d018809 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -30,12 +30,35 @@ struct MissingFeatures { // This isn't needed until we add support for bools. static bool convertTypeForMemory() { return false; } + // CIRGenFunction implementation details + static bool cgfSymbolTable() { return false; } + // Unhandled global/linkage information. static bool opGlobalDSOLocal() { return false; } static bool opGlobalThreadLocal() { return false; } static bool opGlobalConstant() { return false; } static bool opGlobalAlignment() { return false; } static bool opGlobalLinkage() { return false; } + + // Load attributes + static bool opLoadThreadLocal() { return false; } + static bool opLoadEmitScalarRangeCheck() { return false; } + static bool opLoadBooleanRepresentation() { return false; } + + // AllocaOp handling + static bool opAllocaVarDeclContext() { return false; } + static bool opAllocaStaticLocal() { return false; } + static bool opAllocaNonGC() { return false; } + static bool opAllocaImpreciseLifetime() { return false; } + static bool opAllocaPreciseLifetime() { return false; } + static bool opAllocaTLS() { return false; } + static bool opAllocaOpenMPThreadPrivate() { return false; } + static bool opAllocaEscapeByReference() { return false; } + static bool opAllocaReference() { return false; } + + // Misc + static bool scalarConversionOpts() { return false; } + static bool tryEmitAsConstant() { return false; } }; } // namespace cir diff --git a/clang/lib/CIR/CodeGen/Address.h b/clang/lib/CIR/CodeGen/Address.h new file mode 100644 index 0000000000000..72e7e1dcf1560 --- /dev/null +++ b/clang/lib/CIR/CodeGen/Address.h @@ -0,0 +1,76 @@ +//===----------------------------------------------------------------------===// +// +// 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 class provides a simple wrapper for a pair of a pointer and an +// alignment. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_LIB_CIR_ADDRESS_H +#define CLANG_LIB_CIR_ADDRESS_H + +#include "mlir/IR/Value.h" +#include "clang/AST/CharUnits.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/ADT/PointerIntPair.h" + +namespace clang::CIRGen { + +class Address { + + // The boolean flag indicates whether the pointer is known to be non-null. + llvm::PointerIntPair<mlir::Value, 1, bool> pointerAndKnownNonNull; + + /// The expected CIR type of the pointer. Carrying accurate element type + /// information in Address makes it more convenient to work with Address + /// values and allows frontend assertions to catch simple mistakes. + mlir::Type elementType; + + clang::CharUnits alignment; + +protected: + Address(std::nullptr_t) : elementType(nullptr) {} + +public: + Address(mlir::Value pointer, mlir::Type elementType, + clang::CharUnits alignment) + : pointerAndKnownNonNull(pointer, false), elementType(elementType), + alignment(alignment) { + assert(mlir::isa<cir::PointerType>(pointer.getType()) && + "Expected cir.ptr type"); + + assert(pointer && "Pointer cannot be null"); + assert(elementType && "Element type cannot be null"); + assert(!alignment.isZero() && "Alignment cannot be zero"); + + assert(mlir::cast<cir::PointerType>(pointer.getType()).getPointee() == + elementType); + } + + static Address invalid() { return Address(nullptr); } + bool isValid() const { + return pointerAndKnownNonNull.getPointer() != nullptr; + } + + mlir::Value getPointer() const { + assert(isValid()); + return pointerAndKnownNonNull.getPointer(); + } + + mlir::Type getElementType() const { + assert(isValid()); + assert(mlir::cast<cir::PointerType>( + pointerAndKnownNonNull.getPointer().getType()) + .getPointee() == elementType); + return elementType; + } +}; + +} // namespace clang::CIRGen + +#endif // CLANG_LIB_CIR_ADDRESS_H diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp new file mode 100644 index 0000000000000..e44cad559d509 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -0,0 +1,113 @@ +//===----------------------------------------------------------------------===// +// +// 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 Decl nodes as CIR code. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenFunction.h" +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/CIR/MissingFeatures.h" + +using namespace clang; +using namespace clang::CIRGen; + +void 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()); + + if (d.isEscapingByref()) + cgm.errorNYI(d.getSourceRange(), + "emitAutoVarDecl: decl escaping by reference"); + + CharUnits alignment = getContext().getDeclAlign(&d); + + // If the type is variably-modified, emit all the VLA sizes for it. + if (ty->isVariablyModifiedType()) + cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: variably modified type"); + + Address address = Address::invalid(); + if (!ty->isConstantSizeType()) + cgm.errorNYI(d.getSourceRange(), "emitAutoVarDecl: non-constant size type"); + + // A normal fixed sized variable becomes an alloca in the entry block, + mlir::Type allocaTy = convertTypeForMem(ty); + // Create the temp alloca and declare variable using it. + address = createTempAlloca(allocaTy, alignment, loc, d.getName()); + declare(address, &d, ty, getLoc(d.getSourceRange()), alignment); + + setAddrOfLocalVar(&d, address); +} + +void CIRGenFunction::emitAutoVarInit(const clang::VarDecl &d) { + 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"); + } +} + +void CIRGenFunction::emitAutoVarCleanups(const clang::VarDecl &d) { + // Check the type for a cleanup. + if (QualType::DestructionKind dtorKind = d.needsDestruction(getContext())) + cgm.errorNYI(d.getSourceRange(), "emitAutoVarCleanups: type cleanup"); + + assert(!cir::MissingFeatures::opAllocaPreciseLifetime()); + + // Handle the cleanup attribute. + if (d.hasAttr<CleanupAttr>()) + cgm.errorNYI(d.getSourceRange(), "emitAutoVarCleanups: CleanupAttr"); +} + +/// 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 CIRGenFunction::emitAutoVarDecl(const VarDecl &d) { + emitAutoVarAlloca(d); + emitAutoVarInit(d); + emitAutoVarCleanups(d); +} + +void CIRGenFunction::emitVarDecl(const VarDecl &d) { + // If the declaration has external storage, don't emit it now, allow it to be + // emitted lazily on its first use. + if (d.hasExternalStorage()) + return; + + if (d.getStorageDuration() != SD_Automatic) + cgm.errorNYI(d.getSourceRange(), "emitVarDecl automatic storage duration"); + if (d.getType().getAddressSpace() == LangAS::opencl_local) + cgm.errorNYI(d.getSourceRange(), "emitVarDecl openCL address space"); + + assert(d.hasLocalStorage()); + + assert(!cir::MissingFeatures::opAllocaVarDeclContext()); + return emitAutoVarDecl(d); +} + +void CIRGenFunction::emitDecl(const Decl &d) { + switch (d.getKind()) { + case Decl::Var: { + const VarDecl &vd = cast<VarDecl>(d); + assert(vd.isLocalVarDecl() && + "Should not see file-scope variables inside a function!"); + emitVarDecl(vd); + return; + } + default: + cgm.errorNYI(d.getSourceRange(), "emitDecl: unhandled decl type"); + } +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp new file mode 100644 index 0000000000000..ccc3e20875263 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -0,0 +1,130 @@ +//===----------------------------------------------------------------------===// +// +// 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 Expr nodes as CIR code. +// +//===----------------------------------------------------------------------===// + +#include "Address.h" +#include "CIRGenFunction.h" +#include "CIRGenValue.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "clang/AST/Attr.h" +#include "clang/AST/CharUnits.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/MissingFeatures.h" + +using namespace clang; +using namespace clang::CIRGen; +using namespace cir; + +mlir::Value CIRGenFunction::emitLoadOfScalar(LValue lvalue, + SourceLocation loc) { + assert(!cir::MissingFeatures::opLoadThreadLocal()); + assert(!cir::MissingFeatures::opLoadEmitScalarRangeCheck()); + assert(!cir::MissingFeatures::opLoadBooleanRepresentation()); + + Address addr = lvalue.getAddress(); + mlir::Type eltTy = addr.getElementType(); + + mlir::Value ptr = addr.getPointer(); + if (mlir::isa<cir::VoidType>(eltTy)) + cgm.errorNYI(loc, "emitLoadOfScalar: void type"); + + mlir::Value loadOp = builder.CIRBaseBuilderTy::createLoad( + getLoc(loc), ptr, false /*isVolatile*/); + + return loadOp; +} + +/// Given an expression that represents a value lvalue, this +/// method emits the address of the lvalue, then loads the result as an rvalue, +/// returning the rvalue. +RValue CIRGenFunction::emitLoadOfLValue(LValue lv, SourceLocation loc) { + assert(!lv.getType()->isFunctionType()); + assert(!(lv.getType()->isConstantMatrixType()) && "not implemented"); + + if (lv.isSimple()) + return RValue::get(emitLoadOfScalar(lv, loc)); + + cgm.errorNYI(loc, "emitLoadOfLValue"); + return RValue::get(nullptr); +} + +LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) { + const NamedDecl *nd = e->getDecl(); + QualType ty = e->getType(); + + assert(e->isNonOdrUse() != NOUR_Unevaluated && + "should not emit an unevaluated operand"); + + if (const auto *vd = dyn_cast<VarDecl>(nd)) { + // Checks for omitted feature handling + assert(!cir::MissingFeatures::opAllocaStaticLocal()); + assert(!cir::MissingFeatures::opAllocaNonGC()); + assert(!cir::MissingFeatures::opAllocaImpreciseLifetime()); + assert(!cir::MissingFeatures::opAllocaTLS()); + assert(!cir::MissingFeatures::opAllocaOpenMPThreadPrivate()); + assert(!cir::MissingFeatures::opAllocaEscapeByReference()); + + // Check if this is a global variable + if (vd->hasLinkage() || vd->isStaticDataMember()) + cgm.errorNYI(vd->getSourceRange(), "emitDeclRefLValue: global variable"); + + Address addr = Address::invalid(); + + // The variable should generally be present in the local decl map. + auto iter = LocalDeclMap.find(vd); + if (iter != LocalDeclMap.end()) { + addr = iter->second; + } else { + // Otherwise, it might be static local we haven't emitted yet for some + // reason; most likely, because it's in an outer function. + cgm.errorNYI(vd->getSourceRange(), "emitDeclRefLValue: static local"); + } + + return LValue::makeAddr(addr, ty); + } + + cgm.errorNYI(e->getSourceRange(), "emitDeclRefLValue: unhandled decl type"); + return LValue(); +} + +mlir::Value CIRGenFunction::emitAlloca(StringRef name, mlir::Type ty, + mlir::Location loc, + CharUnits alignment) { + mlir::Block *entryBlock = getCurFunctionEntryBlock(); + + // CIR uses its own alloca address space rather than follow the target data + // layout like original CodeGen. The data layout awareness should be done in + // the lowering pass instead. + assert(!cir::MissingFeatures::addressSpace()); + cir::PointerType localVarPtrTy = builder.getPointerTo(ty); + mlir::IntegerAttr alignIntAttr = cgm.getSize(alignment); + + mlir::Value addr; + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(builder.getBestAllocaInsertPoint(entryBlock)); + addr = builder.createAlloca(loc, /*addr type*/ localVarPtrTy, + /*var type*/ ty, name, alignIntAttr); + assert(!cir::MissingFeatures::opAllocaVarDeclContext()); + } + return addr; +} + +/// This creates an alloca and inserts it at the current insertion point of the +/// builder. +Address CIRGenFunction::createTempAlloca(mlir::Type ty, CharUnits align, + mlir::Location loc, + const Twine &name) { + mlir::Value alloca = emitAlloca(name.str(), ty, loc, align); + return Address(alloca, ty, align); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 24a959108f73b..90a2fd2a5d806 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -11,9 +11,11 @@ //===----------------------------------------------------------------------===// #include "CIRGenFunction.h" +#include "CIRGenValue.h" #include "clang/AST/Expr.h" #include "clang/AST/StmtVisitor.h" +#include "clang/CIR/MissingFeatures.h" #include "mlir/IR/Value.h" @@ -52,6 +54,19 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { return {}; } + /// Emits the address of the l-value, then loads and returns the result. + mlir::Value emitLoadOfLValue(const Expr *e) { + LValue lv = cgf.emitLValue(e); + // FIXME: add some akin to EmitLValueAlignmentAssumption(E, V); + return cgf.emitLoadOfLValue(lv, e->getExprLoc()).getScalarVal(); + } + + // l-values + mlir::Value VisitDeclRefExpr(DeclRefExpr *e) { + assert(!cir::MissingFeatures::tryEmitAsConstant()); + return emitLoadOfLValue(e); + } + mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) { mlir::Type type = cgf.convertType(e->getType()); return builder.create<cir::ConstantOp>( @@ -65,7 +80,27 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { cgf.getLoc(e->getExprLoc()), type, builder.getCIRBoolAttr(e->getValue())); } + + mlir::Value VisitCastExpr(CastExpr *E); + + /// Emit a conversion from the specified type to the specified destination + /// type, both of which are CIR scalar types. + /// TODO: do we need ScalarConversionOpts here? Should be done in another + /// pass. + mlir::Value emitScalarConversion(mlir::Value src, QualType srcType, + QualType dstType, SourceLocation loc) { + // No sort of type conversion is implemented yet, but the path for implicit + // paths goes through here even if the type isn't being changed. + srcType = srcType.getCanonicalType(); + dstType = dstType.getCanonicalType(); + if (srcType == dstType) + return src; + + cgf.getCIRGenModule().errorNYI(loc, + "emitScalarConversion for unequal types"); + } }; + } // namespace /// Emit the computation of the specified expression of scalar type. @@ -75,3 +110,31 @@ mlir::Value CIRGenFunction::emitScalarExpr(const Expr *e) { return ScalarExprEmitter(*this, builder).Visit(const_cast<Expr *>(e)); } + +// Emit code for an explicit or implicit cast. Implicit +// casts have to handle a more broad range of conversions than explicit +// casts, as they handle things like function to ptr-to-function decay +// etc. +mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *ce) { + Expr *e = ce->getSubExpr(); + QualType destTy = ce->getType(); + CastKind kind = ce->getCastKind(); + + switch (kind) { + case CK_LValueToRValue: + assert(cgf.getContext().hasSameUnqualifiedType(e->getType(), destTy)); + assert(e->isGLValue() && "lvalue-to-rvalue applied to r-value!"); + return Visit(const_cast<Expr *>(e)); + + case CK_IntegralCast: { + assert(!cir::MissingFeatures::scalarConversionOpts()); + return emitScalarConversion(Visit(e), e->getType(), destTy, + ce->getExprLoc()); + } + + default: + cgf.getCIRGenModule().errorNYI(e->getSourceRange(), + "CastExpr: ", ce->getCastKindName()); + } + return {}; +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index bba2f71a87627..86986b5847e98 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -13,6 +13,7 @@ #include "CIRGenFunction.h" #include "clang/AST/GlobalDecl.h" +#include "clang/CIR/MissingFeatures.h" #include <cassert> @@ -131,6 +132,21 @@ mlir::Location CIRGenFunction::getLoc(mlir::Location lhs, mlir::Location rhs) { return mlir::FusedLoc::get(locs, metadata, &getMLIRContext()); } +mlir::LogicalResult CIRGenFunction::declare(Address addr, const Decl *var, + QualType ty, mlir::Location loc, + CharUnits alignment) { + const auto *namedVar = dyn_cast_or_null<NamedDecl>(var); + assert(namedVar && "Needs a named decl"); + assert(!cir::MissingFeatures::cgfSymbolTable()); + + mlir::Value addrVal = addr.getPointer(); + auto allocaOp = cast<cir::AllocaOp>(addrVal.getDefiningOp()); + if (ty->isReferenceType() || ty.isConstQualified()) + allocaOp.setConstantAttr(mlir::UnitAttr::get(&getMLIRContext())); + + return mlir::success(); +} + void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType, cir::FuncOp fn, cir::FuncType funcType, SourceLocation loc, @@ -153,6 +169,7 @@ mlir::LogicalResult CIRGenFunction::emitFunctionBody(const clang::Stmt *body) { emitCompoundStmtWithoutScope(*block); else result = emitStmt(body, /*useCurrentScope=*/true); + return result; } @@ -217,4 +234,20 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn, return fn; } +/// Emit code to compute a designator that specifies the location +/// of the expression. +/// FIXME: document this function better. +LValue CIRGenFunction::emitLValue(const Expr *e) { + // FIXME: ApplyDebugLocation DL(*this, e); + switch (e->getStmtClass()) { + default: + getCIRGenModule().errorNYI(e->getSourceRange(), + std::string("l-value not implemented for '") + + e->getStmtClassName() + "'"); + break; + case Expr::DeclRefExprClass: + return emitDeclRefLValue(cast<DeclRefExpr>(e)); + } +} + } // namespace clang::CIRGen diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 92fbea16d3aa1..e0888acdc3dce 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -16,8 +16,12 @@ #include "CIRGenBuilder.h" #include "CIRGenModule.h" #include "CIRGenTypeCache.h" +#include "CIRGenValue.h" + +#include "Address.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/CharUnits.h" #include "clang/AST/Decl.h" #include "clang/AST/Type.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" @@ -49,6 +53,11 @@ class CIRGenFunction : public CIRGenTypeCache { /// for. mlir::Operation *curFn = nullptr; + using DeclMapTy = llvm::DenseMap<const clang::Decl *, Address>; + /// This keeps track of the CIR allocas or globals for local C + /// declarations. + DeclMapTy LocalDeclMap; + clang::ASTContext &getContext() const { return cgm.getASTContext(); } CIRGenBuilderTy &getBuilder() { return builder; } @@ -56,6 +65,12 @@ class CIRGenFunction : public CIRGenTypeCache { CIRGenModule &getCIRGenModule() { return cgm; } const CIRGenModule &getCIRGenModule() const { return cgm; } + mlir::Block *getCurFunctionEntryBlock() { + auto fn = mlir::dyn_cast<cir::FuncOp>(curFn); + assert(fn && "other callables NYI"); + return &fn.getRegion().front(); + } + mlir::Type convertTypeForMem(QualType T); mlir::Type convertType(clang::QualType T); @@ -78,6 +93,17 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); } +private: + /// Declare a variable in the current scope, return success if the variable + /// wasn't declared yet. + mlir::LogicalResult declare(Address addr, const clang::Decl *var, + clang::QualType ty, mlir::Location loc, + clang::CharUnits alignment); + +public: + mlir::Value emitAlloca(llvm::StringRef name, mlir::Type ty, + mlir::Location loc, clang::CharUnits alignment); + /// Use to track source locations across nested visitor traversals. /// Always use a `SourceLocRAIIObject` to change currSrcLoc. std::optional<mlir::Location> currSrcLoc; @@ -121,8 +147,50 @@ class CIRGenFunction : public CIRGenTypeCache { void emitCompoundStmtWithoutScope(const clang::CompoundStmt &s); + mlir::LogicalResult emitDeclStmt(const clang::DeclStmt &s); + mlir::LogicalResult emitReturnStmt(const clang::ReturnStmt &s); + /// Given an expression that represents a value lvalue, this method emits + /// the address of the lvalue, then loads the result as an rvalue, + /// returning the rvalue. + RValue emitLoadOfLValue(LValue lv, SourceLocation loc); + + /// EmitLoadOfScalar - Load a scalar value from an address, taking + /// care to appropriately convert from the memory representation to + /// the LLVM value representation. The l-value must be a simple + /// l-value. + mlir::Value emitLoadOfScalar(LValue lvalue, SourceLocation loc); + + /// Emit code to compute a designator that specifies the location + /// of the expression. + /// FIXME: document this function better. + LValue emitLValue(const clang::Expr *e); + + void emitDecl(const clang::Decl &d); + + LValue emitDeclRefLValue(const clang::DeclRefExpr *e); + + /// 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); + + /// This method handles emission of any variable declaration + /// inside a function, including static vars etc. + void emitVarDecl(const clang::VarDecl &d); + + /// Set the address of a local variable. + void setAddrOfLocalVar(const clang::VarDecl *vd, Address addr) { + assert(!LocalDeclMap.count(vd) && "Decl already exists in LocalDeclMap!"); + LocalDeclMap.insert({vd, addr}); + // TODO: Add symbol table support + } + /// Emit the computation of the specified expression of scalar type. mlir::Value emitScalarExpr(const clang::Expr *e); cir::FuncOp generateCode(clang::GlobalDecl gd, cir::FuncOp fn, @@ -134,8 +202,10 @@ class CIRGenFunction : public CIRGenTypeCache { void startFunction(clang::GlobalDecl gd, clang::QualType retTy, cir::FuncOp fn, cir::FuncType funcType, clang::SourceLocation loc, clang::SourceLocation startLoc); -}; + Address createTempAlloca(mlir::Type ty, CharUnits align, mlir::Location loc, + const Twine &name = "tmp"); +}; } // namespace clang::CIRGen #endif diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index bf3a4d1130f15..71a37b8c9a2ea 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -17,6 +17,7 @@ #include "CIRGenTypeCache.h" #include "CIRGenTypes.h" +#include "clang/AST/CharUnits.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "mlir/IR/Builders.h" @@ -116,6 +117,10 @@ class CIRGenModule : public CIRGenTypeCache { cir::FuncType funcType, const clang::FunctionDecl *funcDecl); + mlir::IntegerAttr getSize(CharUnits size) { + return builder.getSizeFromCharUnits(&getMLIRContext(), size); + } + const llvm::Triple &getTriple() const { return target.getTriple(); } /// Helpers to emit "not yet implemented" error diagnostics diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index f42f30cc5a433..ed5d87a39704a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -68,6 +68,8 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s, default: // Only compound and return statements are supported right now. return mlir::failure(); + case Stmt::DeclStmtClass: + return emitDeclStmt(cast<DeclStmt>(*s)); case Stmt::CompoundStmtClass: if (useCurrentScope) emitCompoundStmtWithoutScope(cast<CompoundStmt>(*s)); @@ -81,6 +83,15 @@ mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s, return mlir::success(); } +mlir::LogicalResult CIRGenFunction::emitDeclStmt(const DeclStmt &s) { + assert(builder.getInsertionBlock() && "expected valid insertion point"); + + for (const Decl *I : s.decls()) + emitDecl(*I); + + return mlir::success(); +} + mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) { mlir::Location loc = getLoc(s.getSourceRange()); const Expr *rv = s.getRetValue(); diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h new file mode 100644 index 0000000000000..d29646983fd30 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -0,0 +1,125 @@ +//===----------------------------------------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// These classes implement wrappers around mlir::Value in order to fully +// represent the range of values for C L- and R- values. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_LIB_CIR_CIRGENVALUE_H +#define CLANG_LIB_CIR_CIRGENVALUE_H + +#include "Address.h" + +#include "clang/AST/CharUnits.h" +#include "clang/AST/Type.h" + +#include "llvm/ADT/PointerIntPair.h" + +#include "mlir/IR/Value.h" + +namespace clang::CIRGen { + +/// This trivial value class is used to represent the result of an +/// expression that is evaluated. It can be one of three things: either a +/// simple MLIR SSA value, a pair of SSA values for complex numbers, or the +/// address of an aggregate value in memory. +class RValue { + enum Flavor { Scalar, Complex, Aggregate }; + + // Stores first value and flavor. + llvm::PointerIntPair<mlir::Value, 2, Flavor> v1; + // Stores second value and volatility. + llvm::PointerIntPair<llvm::PointerUnion<mlir::Value, int *>, 1, bool> v2; + // Stores element type for aggregate values. + mlir::Type elementType; + +public: + bool isScalar() const { return v1.getInt() == Scalar; } + + /// Return the mlir::Value of this scalar value. + mlir::Value getScalarVal() const { + assert(isScalar() && "Not a scalar!"); + return v1.getPointer(); + } + + static RValue get(mlir::Value v) { + RValue er; + er.v1.setPointer(v); + er.v1.setInt(Scalar); + er.v2.setInt(false); + return er; + } +}; + +/// The source of the alignment of an l-value; an expression of +/// confidence in the alignment actually matching the estimate. +enum class AlignmentSource { + /// The l-value was an access to a declared entity or something + /// equivalently strong, like the address of an array allocated by a + /// language runtime. + Decl, + + /// The l-value was considered opaque, so the alignment was + /// determined from a type, but that type was an explicitly-aligned + /// typedef. + AttributedType, + + /// The l-value was considered opaque, so the alignment was + /// determined from a type. + Type +}; + +class LValue { + enum { + Simple, // This is a normal l-value, use getAddress(). + VectorElt, // This is a vector element l-value (V[i]), use getVector* + BitField, // This is a bitfield l-value, use getBitfield*. + ExtVectorElt, // This is an extended vector subset, use getExtVectorComp + GlobalReg, // This is a register l-value, use getGlobalReg() + MatrixElt // This is a matrix element, use getVector* + } lvType; + clang::QualType type; + + mlir::Value v; + mlir::Type elementType; + + void initialize(clang::QualType type) { this->type = type; } + +public: + bool isSimple() const { return lvType == Simple; } + + // TODO: Add support for volatile + bool isVolatile() const { return false; } + + clang::QualType getType() const { return type; } + + mlir::Value getPointer() const { return v; } + + clang::CharUnits getAlignment() const { + // TODO: Handle alignment + return clang::CharUnits::One(); + } + + Address getAddress() const { + return Address(getPointer(), elementType, getAlignment()); + } + + static LValue makeAddr(Address address, clang::QualType t) { + LValue r; + r.lvType = Simple; + r.v = address.getPointer(); + r.elementType = address.getElementType(); + r.initialize(t); + return r; + } +}; + +} // namespace clang::CIRGen + +#endif // CLANG_LIB_CIR_CIRGENVALUE_H diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt index 5602efae1ba41..dbb6d9e7b3807 100644 --- a/clang/lib/CIR/CodeGen/CMakeLists.txt +++ b/clang/lib/CIR/CodeGen/CMakeLists.txt @@ -8,6 +8,8 @@ get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) add_clang_library(clangCIR CIRGenerator.cpp + CIRGenDecl.cpp + CIRGenExpr.cpp CIRGenExprScalar.cpp CIRGenFunction.cpp CIRGenModule.cpp diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 3f1be930d71e5..aa21edcb5e99d 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -117,6 +117,24 @@ static void printOmittedTerminatorRegion(mlir::OpAsmPrinter &printer, /*printBlockTerminators=*/!omitRegionTerm(region)); } +//===----------------------------------------------------------------------===// +// AllocaOp +//===----------------------------------------------------------------------===// + +void cir::AllocaOp::build(mlir::OpBuilder &odsBuilder, + mlir::OperationState &odsState, mlir::Type addr, + mlir::Type allocaType, llvm::StringRef name, + mlir::IntegerAttr alignment) { + odsState.addAttribute(getAllocaTypeAttrName(odsState.name), + mlir::TypeAttr::get(allocaType)); + odsState.addAttribute(getNameAttrName(odsState.name), + odsBuilder.getStringAttr(name)); + if (alignment) { + odsState.addAttribute(getAlignmentAttrName(odsState.name), alignment); + } + odsState.addTypes(addr); +} + //===----------------------------------------------------------------------===// // ConstantOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp new file mode 100644 index 0000000000000..af6b5e4fbd9f6 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/CIRMemorySlot.cpp @@ -0,0 +1,77 @@ +//===----------------------------------------------------------------------===// +// +// 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 file implements MemorySlot-related interfaces for CIR dialect +// operations. +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +using namespace mlir; + +//===----------------------------------------------------------------------===// +// Interfaces for AllocaOp +//===----------------------------------------------------------------------===// + +llvm::SmallVector<MemorySlot> cir::AllocaOp::getPromotableSlots() { + return {MemorySlot{getResult(), getAllocaType()}}; +} + +Value cir::AllocaOp::getDefaultValue(const MemorySlot &slot, + OpBuilder &builder) { + return builder.create<cir::ConstantOp>( + getLoc(), slot.elemType, builder.getAttr<cir::UndefAttr>(slot.elemType)); +} + +void cir::AllocaOp::handleBlockArgument(const MemorySlot &slot, + BlockArgument argument, + OpBuilder &builder) {} + +std::optional<PromotableAllocationOpInterface> +cir::AllocaOp::handlePromotionComplete(const MemorySlot &slot, + Value defaultValue, OpBuilder &builder) { + if (defaultValue && defaultValue.use_empty()) + defaultValue.getDefiningOp()->erase(); + this->erase(); + return std::nullopt; +} + +//===----------------------------------------------------------------------===// +// Interfaces for LoadOp +//===----------------------------------------------------------------------===// + +bool cir::LoadOp::loadsFrom(const MemorySlot &slot) { + return getAddr() == slot.ptr; +} + +bool cir::LoadOp::storesTo(const MemorySlot &slot) { return false; } + +Value cir::LoadOp::getStored(const MemorySlot &slot, OpBuilder &builder, + Value reachingDef, const DataLayout &dataLayout) { + llvm_unreachable("getStored should not be called on LoadOp"); +} + +bool cir::LoadOp::canUsesBeRemoved( + const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses, + SmallVectorImpl<OpOperand *> &newBlockingUses, + const DataLayout &dataLayout) { + if (blockingUses.size() != 1) + return false; + Value blockingUse = (*blockingUses.begin())->get(); + return blockingUse == slot.ptr && getAddr() == slot.ptr && + getResult().getType() == slot.elemType; +} + +DeletionKind cir::LoadOp::removeBlockingUses( + const MemorySlot &slot, const SmallPtrSetImpl<OpOperand *> &blockingUses, + OpBuilder &builder, Value reachingDefinition, + const DataLayout &dataLayout) { + getResult().replaceAllUsesWith(reachingDefinition); + return DeletionKind::Delete; +} diff --git a/clang/lib/CIR/Dialect/IR/CMakeLists.txt b/clang/lib/CIR/Dialect/IR/CMakeLists.txt index baf8bff185221..925af0d61c984 100644 --- a/clang/lib/CIR/Dialect/IR/CMakeLists.txt +++ b/clang/lib/CIR/Dialect/IR/CMakeLists.txt @@ -1,6 +1,7 @@ add_clang_library(MLIRCIR CIRAttrs.cpp CIRDialect.cpp + CIRMemorySlot.cpp CIRTypes.cpp DEPENDS diff --git a/clang/test/CIR/CodeGen/basic.cpp b/clang/test/CIR/CodeGen/basic.cpp new file mode 100644 index 0000000000000..210afcd541159 --- /dev/null +++ b/clang/test/CIR/CodeGen/basic.cpp @@ -0,0 +1,27 @@ +// 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 + +int f1() { + int i; + return i; +} + +// CHECK: module +// CHECK: cir.func @f1() -> !cir.int<s, 32> +// CHECK: %[[I_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i"] {alignment = 4 : i64} +// 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 f2() { + const int i = 2; + return i; +} + +// 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:.*]] = cir.load %[[I_PTR]] : !cir.ptr<!cir.int<s, 32>>, !cir.int<s, 32> +// CHECK: cir.return %[[I]] : !cir.int<s, 32> _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits