//===----------------------------------------------------------------------===// // // 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 // //===----------------------------------------------------------------------===// // // Internal per-function state used for AST-to-ClangIR code gen // //===----------------------------------------------------------------------===// #ifndef CLANG_LIB_CIR_CODEGEN_CIRGENFUNCTION_H #define CLANG_LIB_CIR_CODEGEN_CIRGENFUNCTION_H #include "CIRGenBuilder.h" #include "CIRGenCall.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" #include "clang/CIR/MissingFeatures.h" #include "clang/CIR/TypeEvaluationKind.h" namespace { class ScalarExprEmitter; } // namespace namespace clang::CIRGen { class CIRGenFunction : public CIRGenTypeCache { public: CIRGenModule &cgm; private: friend class ::ScalarExprEmitter; /// The builder is a helper class to create IR inside a function. The /// builder is stateful, in particular it keeps an "insertion point": this /// is where the next operations will be introduced. CIRGenBuilderTy &builder; public: /// The GlobalDecl for the current function being compiled or the global /// variable currently being initialized. clang::GlobalDecl curGD; /// The compiler-generated variable that holds the return value. std::optional fnRetAlloca; /// The function for which code is currently being generated. cir::FuncOp curFn; using DeclMapTy = llvm::DenseMap; /// 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; } CIRGenModule &getCIRGenModule() { return cgm; } const CIRGenModule &getCIRGenModule() const { return cgm; } mlir::Block *getCurFunctionEntryBlock() { return &curFn.getRegion().front(); } /// Sanitizers enabled for this function. clang::SanitizerSet sanOpts; /// Whether or not a Microsoft-style asm block has been processed within /// this fuction. These can potentially set the return value. bool sawAsmBlock = false; mlir::Type convertTypeForMem(QualType t); mlir::Type convertType(clang::QualType t); mlir::Type convertType(const TypeDecl *t) { return convertType(getContext().getTypeDeclType(t)); } /// Return the cir::TypeEvaluationKind of QualType \c type. static cir::TypeEvaluationKind getEvaluationKind(clang::QualType type); static bool hasScalarEvaluationKind(clang::QualType type) { return getEvaluationKind(type) == cir::TEK_Scalar; } CIRGenFunction(CIRGenModule &cgm, CIRGenBuilderTy &builder, bool suppressNewContext = false); ~CIRGenFunction(); CIRGenTypes &getTypes() const { return cgm.getTypes(); } mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); } private: /// Declare a variable in the current scope, return success if the variable /// wasn't declared yet. void declare(mlir::Value addrVal, const clang::Decl *var, clang::QualType ty, mlir::Location loc, clang::CharUnits alignment, bool isParam = false); public: mlir::Value createDummyValue(mlir::Location loc, clang::QualType qt); void emitNullInitialization(mlir::Location loc, Address destPtr, QualType ty); 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 currSrcLoc; class SourceLocRAIIObject { CIRGenFunction &cgf; std::optional oldLoc; public: SourceLocRAIIObject(CIRGenFunction &cgf, mlir::Location value) : cgf(cgf) { if (cgf.currSrcLoc) oldLoc = cgf.currSrcLoc; cgf.currSrcLoc = value; } /// Can be used to restore the state early, before the dtor /// is run. void restore() { cgf.currSrcLoc = oldLoc; } ~SourceLocRAIIObject() { restore(); } }; /// Helpers to convert Clang's SourceLocation to a MLIR Location. mlir::Location getLoc(clang::SourceLocation srcLoc); mlir::Location getLoc(clang::SourceRange srcLoc); mlir::Location getLoc(mlir::Location lhs, mlir::Location rhs); const clang::LangOptions &getLangOpts() const { return cgm.getLangOpts(); } void finishFunction(SourceLocation endLoc); /// Determine whether the given initializer is trivial in the sense /// that it requires no code to be generated. bool isTrivialInitializer(const Expr *init); 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(); } }; /// Perform the usual unary conversions on the specified expression and /// compare the result against zero, returning an Int1Ty value. mlir::Value evaluateExprAsBool(const clang::Expr *e); /// 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 } cir::FuncOp generateCode(clang::GlobalDecl gd, cir::FuncOp fn, cir::FuncType funcType); clang::QualType buildFunctionArgList(clang::GlobalDecl gd, FunctionArgList &args); /// Emit code for the start of a function. /// \param loc The location to be associated with the function. /// \param startLoc The location of the function body. void startFunction(clang::GlobalDecl gd, clang::QualType returnType, cir::FuncOp fn, cir::FuncType funcType, FunctionArgList args, clang::SourceLocation loc, clang::SourceLocation startLoc); /// Represents a scope, including function bodies, compound statements, and /// the substatements of if/while/do/for/switch/try statements. This class /// handles any automatic cleanup, along with the return value. struct LexicalScope { private: // TODO(CIR): This will live in the base class RunCleanupScope once that // class is upstreamed. CIRGenFunction &cgf; // Points to the scope entry block. This is useful, for instance, for // helping to insert allocas before finalizing any recursive CodeGen from // switches. mlir::Block *entryBlock; LexicalScope *parentScope = nullptr; // Only Regular is used at the moment. Support for other kinds will be // added as the relevant statements/expressions are upstreamed. enum Kind { Regular, // cir.if, cir.scope, if_regions Ternary, // cir.ternary Switch, // cir.switch Try, // cir.try GlobalInit // cir.global initialization code }; Kind scopeKind = Kind::Regular; // The scope return value. mlir::Value retVal = nullptr; mlir::Location beginLoc; mlir::Location endLoc; public: unsigned depth = 0; LexicalScope(CIRGenFunction &cgf, mlir::Location loc, mlir::Block *eb) : cgf(cgf), entryBlock(eb), parentScope(cgf.curLexScope), beginLoc(loc), endLoc(loc) { assert(entryBlock && "LexicalScope requires an entry block"); cgf.curLexScope = this; if (parentScope) ++depth; if (const auto fusedLoc = mlir::dyn_cast(loc)) { assert(fusedLoc.getLocations().size() == 2 && "too many locations"); beginLoc = fusedLoc.getLocations()[0]; endLoc = fusedLoc.getLocations()[1]; } } void setRetVal(mlir::Value v) { retVal = v; } void cleanup(); void restore() { cgf.curLexScope = parentScope; } ~LexicalScope() { assert(!cir::MissingFeatures::generateDebugInfo()); cleanup(); restore(); } // --- // Kind // --- bool isGlobalInit() { return scopeKind == Kind::GlobalInit; } bool isRegular() { return scopeKind == Kind::Regular; } bool isSwitch() { return scopeKind == Kind::Switch; } bool isTernary() { return scopeKind == Kind::Ternary; } bool isTry() { return scopeKind == Kind::Try; } void setAsGlobalInit() { scopeKind = Kind::GlobalInit; } void setAsSwitch() { scopeKind = Kind::Switch; } void setAsTernary() { scopeKind = Kind::Ternary; } // --- // Return handling. // --- private: // `returnBlock`, `returnLoc`, and all the functions that deal with them // will change and become more complicated when `switch` statements are // upstreamed. `case` statements within the `switch` are in the same scope // but have their own regions. Therefore the LexicalScope will need to // keep track of multiple return blocks. mlir::Block *returnBlock = nullptr; std::optional returnLoc; // See the comment on `getOrCreateRetBlock`. mlir::Block *createRetBlock(CIRGenFunction &cgf, mlir::Location loc) { assert(returnBlock == nullptr && "only one return block per scope"); // Create the cleanup block but don't hook it up just yet. mlir::OpBuilder::InsertionGuard guard(cgf.builder); returnBlock = cgf.builder.createBlock(cgf.builder.getBlock()->getParent()); updateRetLoc(returnBlock, loc); return returnBlock; } cir::ReturnOp emitReturn(mlir::Location loc); void emitImplicitReturn(); public: mlir::Block *getRetBlock() { return returnBlock; } mlir::Location getRetLoc(mlir::Block *b) { return *returnLoc; } void updateRetLoc(mlir::Block *b, mlir::Location loc) { returnLoc = loc; } // Create the return block for this scope, or return the existing one. // This get-or-create logic is necessary to handle multiple return // statements within the same scope, which can happen if some of them are // dead code or if there is a `goto` into the middle of the scope. mlir::Block *getOrCreateRetBlock(CIRGenFunction &cgf, mlir::Location loc) { if (returnBlock == nullptr) { returnBlock = createRetBlock(cgf, loc); return returnBlock; } updateRetLoc(returnBlock, loc); return returnBlock; } mlir::Block *getEntryBlock() { return entryBlock; } }; LexicalScope *curLexScope = nullptr; /// ---------------------- /// CIR emit functions /// ---------------------- private: void emitAndUpdateRetAlloca(clang::QualType type, mlir::Location loc, clang::CharUnits alignment); public: mlir::Value emitAlloca(llvm::StringRef name, mlir::Type ty, mlir::Location loc, clang::CharUnits alignment, bool insertIntoFnEntryBlock, mlir::Value arraySize = nullptr); mlir::Value emitAlloca(llvm::StringRef name, mlir::Type ty, mlir::Location loc, clang::CharUnits alignment, mlir::OpBuilder::InsertPoint ip, mlir::Value arraySize = nullptr); void emitAggExpr(const clang::Expr *e, AggValueSlot slot); /// Emit code to compute the specified expression which can have any type. The /// result is returned as an RValue struct. If this is an aggregate /// expression, the aggloc/agglocvolatile arguments indicate where the result /// should be returned. RValue emitAnyExpr(const clang::Expr *e); AutoVarEmission emitAutoVarAlloca(const clang::VarDecl &d); /// 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 emitAutoVarCleanups(const AutoVarEmission &emission); void emitAutoVarInit(const AutoVarEmission &emission); LValue emitBinaryOperatorLValue(const BinaryOperator *e); mlir::LogicalResult emitDoStmt(const clang::DoStmt &s); /// 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); mlir::LogicalResult emitFunctionBody(const clang::Stmt *body); mlir::Value emitPromotedScalarExpr(const Expr *e, QualType promotionType); /// Emit the computation of the specified expression of scalar type. mlir::Value emitScalarExpr(const clang::Expr *e); mlir::Value emitScalarPrePostIncDec(const UnaryOperator *e, LValue lv, bool isInc, bool isPre); /// Build a debug stoppoint if we are emitting debug info. void emitStopPoint(const Stmt *s); // Build CIR for a statement. useCurrentScope should be true if no // new scopes need be created when finding a compound statement. mlir::LogicalResult emitStmt(const clang::Stmt *s, bool useCurrentScope, llvm::ArrayRef attrs = std::nullopt); mlir::LogicalResult emitSimpleStmt(const clang::Stmt *s, bool useCurrentScope); mlir::LogicalResult emitForStmt(const clang::ForStmt &s); void emitCompoundStmt(const clang::CompoundStmt &s); void emitCompoundStmtWithoutScope(const clang::CompoundStmt &s); void emitDecl(const clang::Decl &d); mlir::LogicalResult emitDeclStmt(const clang::DeclStmt &s); LValue emitDeclRefLValue(const clang::DeclRefExpr *e); /// Emit code to compute the specified expression, /// ignoring the result. void emitIgnoredExpr(const clang::Expr *e); /// 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); mlir::LogicalResult emitReturnStmt(const clang::ReturnStmt &s); /// Emit a conversion from the specified type to the specified destination /// type, both of which are CIR scalar types. mlir::Value emitScalarConversion(mlir::Value src, clang::QualType srcType, clang::QualType dstType, clang::SourceLocation loc); void emitScalarInit(const clang::Expr *init, mlir::Location loc, LValue lvalue, bool capturedByInit = false); 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); /// 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); /// 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); LValue emitUnaryOpLValue(const clang::UnaryOperator *e); /// This method handles emission of any variable declaration /// inside a function, including static vars etc. void emitVarDecl(const clang::VarDecl &d); mlir::LogicalResult emitWhileStmt(const clang::WhileStmt &s); /// ---------------------- /// CIR build helpers /// ----------------- public: Address createTempAlloca(mlir::Type ty, CharUnits align, mlir::Location loc, const Twine &name, bool insertIntoFnEntryBlock); }; } // namespace clang::CIRGen #endif