From 88d2947d97442f5ced5ded124f90ced466e6947f Mon Sep 17 00:00:00 2001 From: zhaoxuhui Date: Thu, 16 Nov 2023 09:30:45 +0800 Subject: [PATCH 1/2] [BSC] introduce CIR --- clang/CMakeLists.txt | 2 +- clang/include/clang/Basic/Builtins.def | 8 + clang/include/clang/Basic/CodeGenOptions.def | 10 + clang/include/clang/Basic/LangOptions.def | 2 + clang/include/clang/Basic/LangStandard.h | 1 + clang/include/clang/CIR/CIRGenerator.h | 107 + clang/include/clang/CIR/CIRToCIRPasses.h | 39 + clang/include/clang/CIR/CMakeLists.txt | 8 + .../include/clang/CIR/Dialect/CMakeLists.txt | 28 + .../clang/CIR/Dialect/IR/CIRAttrDefs.td | 0 clang/include/clang/CIR/Dialect/IR/CIRAttrs.h | 46 + .../include/clang/CIR/Dialect/IR/CIRAttrs.td | 524 ++++ .../include/clang/CIR/Dialect/IR/CIRDialect.h | 69 + .../clang/CIR/Dialect/IR/CIRDialect.td | 46 + clang/include/clang/CIR/Dialect/IR/CIROps.td | 2198 +++++++++++++ .../clang/CIR/Dialect/IR/CIROpsEnums.h | 121 + clang/include/clang/CIR/Dialect/IR/CIRTypes.h | 29 + .../include/clang/CIR/Dialect/IR/CIRTypes.td | 270 ++ .../clang/CIR/Dialect/IR/CMakeLists.txt | 29 + clang/include/clang/CIR/Dialect/IR/FPEnv.h | 50 + clang/include/clang/CIR/Dialect/Passes.h | 44 + clang/include/clang/CIR/Dialect/Passes.td | 78 + .../CIR/Dialect/Transforms/CMakeLists.txt | 0 .../clang/CIR/Interfaces/ASTAttrInterfaces.h | 45 + .../clang/CIR/Interfaces/ASTAttrInterfaces.td | 191 ++ .../clang/CIR/Interfaces/CMakeLists.txt | 15 + clang/include/clang/CIR/Passes.h | 34 + .../clang/CIRFrontendAction/CIRGenAction.h | 117 + clang/include/clang/CMakeLists.txt | 1 + clang/include/clang/Driver/Options.td | 49 +- clang/include/clang/Frontend/FrontendAction.h | 3 + .../include/clang/Frontend/FrontendOptions.h | 36 + clang/lib/Basic/LangStandards.cpp | 1 + clang/lib/CIR/CMakeLists.txt | 7 + clang/lib/CIR/CodeGen/ABIInfo.h | 45 + clang/lib/CIR/CodeGen/Address.h | 117 + clang/lib/CIR/CodeGen/CIRDataLayout.h | 82 + clang/lib/CIR/CodeGen/CIRGenBuilder.h | 806 +++++ clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp | 691 ++++ clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 138 + clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp | 74 + clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 307 ++ clang/lib/CIR/CodeGen/CIRGenCall.cpp | 1345 ++++++++ clang/lib/CIR/CodeGen/CIRGenCall.h | 291 ++ clang/lib/CIR/CodeGen/CIRGenClass.cpp | 1293 ++++++++ clang/lib/CIR/CodeGen/CIRGenCleanup.cpp | 471 +++ clang/lib/CIR/CodeGen/CIRGenCleanup.h | 617 ++++ clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp | 584 ++++ clang/lib/CIR/CodeGen/CIRGenCstEmitter.h | 153 + clang/lib/CIR/CodeGen/CIRGenDecl.cpp | 973 ++++++ clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp | 86 + clang/lib/CIR/CodeGen/CIRGenException.cpp | 454 +++ clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 2776 +++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp | 1144 +++++++ clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 913 ++++++ clang/lib/CIR/CodeGen/CIRGenExprConst.cpp | 1566 ++++++++++ clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 2300 ++++++++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 1383 ++++++++ clang/lib/CIR/CodeGen/CIRGenFunction.h | 1946 ++++++++++++ clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h | 476 +++ clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 1992 ++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 2623 ++++++++++++++++ clang/lib/CIR/CodeGen/CIRGenModule.h | 630 ++++ clang/lib/CIR/CodeGen/CIRGenRecordLayout.h | 204 ++ clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 1090 +++++++ clang/lib/CIR/CodeGen/CIRGenTBAA.cpp | 0 clang/lib/CIR/CodeGen/CIRGenTBAA.h | 0 clang/lib/CIR/CodeGen/CIRGenTypeCache.h | 130 + clang/lib/CIR/CodeGen/CIRGenTypes.cpp | 860 +++++ clang/lib/CIR/CodeGen/CIRGenTypes.h | 269 ++ clang/lib/CIR/CodeGen/CIRGenVTables.cpp | 586 ++++ clang/lib/CIR/CodeGen/CIRGenVTables.h | 178 ++ clang/lib/CIR/CodeGen/CIRGenValue.h | 487 +++ clang/lib/CIR/CodeGen/CIRGenerator.cpp | 190 ++ clang/lib/CIR/CodeGen/CIRPasses.cpp | 50 + .../CIR/CodeGen/CIRRecordLayoutBuilder.cpp | 684 ++++ clang/lib/CIR/CodeGen/CMakeLists.txt | 70 + clang/lib/CIR/CodeGen/CallingConv.h | 43 + clang/lib/CIR/CodeGen/ConstantInitBuilder.cpp | 327 ++ clang/lib/CIR/CodeGen/ConstantInitBuilder.h | 589 ++++ clang/lib/CIR/CodeGen/ConstantInitFuture.h | 102 + clang/lib/CIR/CodeGen/EHScopeStack.h | 426 +++ clang/lib/CIR/CodeGen/TargetInfo.cpp | 460 +++ clang/lib/CIR/CodeGen/TargetInfo.h | 38 + .../CodeGen/UnimplementedFeatureGuarding.h | 145 + clang/lib/CIR/Dialect/CMakeLists.txt | 2 + clang/lib/CIR/Dialect/IR/CIRAttrs.cpp | 306 ++ clang/lib/CIR/Dialect/IR/CIRDialect.cpp | 2461 +++++++++++++++ clang/lib/CIR/Dialect/IR/CIRTypes.cpp | 510 +++ clang/lib/CIR/Dialect/IR/CMakeLists.txt | 20 + clang/lib/CIR/Dialect/IR/FPEnv.cpp | 64 + .../lib/CIR/Dialect/Transforms/CMakeLists.txt | 21 + clang/lib/CIR/Dialect/Transforms/DropAST.cpp | 50 + .../CIR/Dialect/Transforms/LifetimeCheck.cpp | 1983 ++++++++++++ .../Dialect/Transforms/LoweringPrepare.cpp | 336 ++ .../CIR/Dialect/Transforms/MergeCleanups.cpp | 256 ++ clang/lib/CIR/Dialect/Transforms/PassDetail.h | 29 + clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 449 +++ clang/lib/CIR/FrontendAction/CMakeLists.txt | 36 + .../lib/CIR/Interfaces/ASTAttrInterfaces.cpp | 15 + clang/lib/CIR/Interfaces/CMakeLists.txt | 14 + clang/lib/CMakeLists.txt | 1 + clang/lib/Driver/ToolChains/Clang.cpp | 10 + .../Serialization/SymbolGraphSerializer.cpp | 1 + clang/lib/Frontend/CompilerInvocation.cpp | 30 + clang/lib/Frontend/FrontendAction.cpp | 20 + clang/lib/FrontendTool/CMakeLists.txt | 14 + .../ExecuteCompilerInvocation.cpp | 56 +- clang/tools/CMakeLists.txt | 3 + llvm/CMakeLists.txt | 22 +- llvm/tools/CMakeLists.txt | 3 +- mlir/CMakeLists.txt | 5 + .../mlir/IR/BuiltinAttributeInterfaces.td | 19 + mlir/include/mlir/IR/OpBase.td | 61 +- .../mlir/Interfaces/SideEffectInterfaces.h | 23 + .../mlir/Interfaces/SideEffectInterfaces.td | 52 + 116 files changed, 44268 insertions(+), 16 deletions(-) create mode 100644 clang/include/clang/CIR/CIRGenerator.h create mode 100644 clang/include/clang/CIR/CIRToCIRPasses.h create mode 100644 clang/include/clang/CIR/CMakeLists.txt create mode 100644 clang/include/clang/CIR/Dialect/CMakeLists.txt create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRAttrDefs.td create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRAttrs.h create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRAttrs.td create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRDialect.h create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRDialect.td create mode 100644 clang/include/clang/CIR/Dialect/IR/CIROps.td create mode 100644 clang/include/clang/CIR/Dialect/IR/CIROpsEnums.h create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRTypes.h create mode 100644 clang/include/clang/CIR/Dialect/IR/CIRTypes.td create mode 100644 clang/include/clang/CIR/Dialect/IR/CMakeLists.txt create mode 100644 clang/include/clang/CIR/Dialect/IR/FPEnv.h create mode 100644 clang/include/clang/CIR/Dialect/Passes.h create mode 100644 clang/include/clang/CIR/Dialect/Passes.td create mode 100644 clang/include/clang/CIR/Dialect/Transforms/CMakeLists.txt create mode 100644 clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h create mode 100644 clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td create mode 100644 clang/include/clang/CIR/Interfaces/CMakeLists.txt create mode 100644 clang/include/clang/CIR/Passes.h create mode 100644 clang/include/clang/CIRFrontendAction/CIRGenAction.h create mode 100644 clang/lib/CIR/CMakeLists.txt create mode 100644 clang/lib/CIR/CodeGen/ABIInfo.h create mode 100644 clang/lib/CIR/CodeGen/Address.h create mode 100644 clang/lib/CIR/CodeGen/CIRDataLayout.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenBuilder.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCXX.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCXXABI.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenCall.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCall.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenClass.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCleanup.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCleanup.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenCstEmitter.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenDecl.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenException.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenExpr.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenExprConst.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenFunction.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenFunction.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenModule.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenModule.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenRecordLayout.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenStmt.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenTBAA.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenTBAA.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenTypeCache.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenTypes.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenTypes.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenVTables.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRGenVTables.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenValue.h create mode 100644 clang/lib/CIR/CodeGen/CIRGenerator.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRPasses.cpp create mode 100644 clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp create mode 100644 clang/lib/CIR/CodeGen/CMakeLists.txt create mode 100644 clang/lib/CIR/CodeGen/CallingConv.h create mode 100644 clang/lib/CIR/CodeGen/ConstantInitBuilder.cpp create mode 100644 clang/lib/CIR/CodeGen/ConstantInitBuilder.h create mode 100644 clang/lib/CIR/CodeGen/ConstantInitFuture.h create mode 100644 clang/lib/CIR/CodeGen/EHScopeStack.h create mode 100644 clang/lib/CIR/CodeGen/TargetInfo.cpp create mode 100644 clang/lib/CIR/CodeGen/TargetInfo.h create mode 100644 clang/lib/CIR/CodeGen/UnimplementedFeatureGuarding.h create mode 100644 clang/lib/CIR/Dialect/CMakeLists.txt create mode 100644 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp create mode 100644 clang/lib/CIR/Dialect/IR/CIRDialect.cpp create mode 100644 clang/lib/CIR/Dialect/IR/CIRTypes.cpp create mode 100644 clang/lib/CIR/Dialect/IR/CMakeLists.txt create mode 100644 clang/lib/CIR/Dialect/IR/FPEnv.cpp create mode 100644 clang/lib/CIR/Dialect/Transforms/CMakeLists.txt create mode 100644 clang/lib/CIR/Dialect/Transforms/DropAST.cpp create mode 100644 clang/lib/CIR/Dialect/Transforms/LifetimeCheck.cpp create mode 100644 clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp create mode 100644 clang/lib/CIR/Dialect/Transforms/MergeCleanups.cpp create mode 100644 clang/lib/CIR/Dialect/Transforms/PassDetail.h create mode 100644 clang/lib/CIR/FrontendAction/CIRGenAction.cpp create mode 100644 clang/lib/CIR/FrontendAction/CMakeLists.txt create mode 100644 clang/lib/CIR/Interfaces/ASTAttrInterfaces.cpp create mode 100644 clang/lib/CIR/Interfaces/CMakeLists.txt diff --git a/clang/CMakeLists.txt b/clang/CMakeLists.txt index 08a80bcbc97c..4966c607afda 100644 --- a/clang/CMakeLists.txt +++ b/clang/CMakeLists.txt @@ -11,7 +11,7 @@ endif() include(GNUInstallDirs) if(CLANG_BUILT_STANDALONE) - set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard to conform to") + set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to conform to") set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_CXX_EXTENSIONS NO) diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def index f19807dbbb0b..a16ac608ae7c 100644 --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -599,8 +599,15 @@ BUILTIN(__builtin_unwind_init, "v", "") BUILTIN(__builtin_eh_return_data_regno, "iIi", "nc") BUILTIN(__builtin_snprintf, "ic*zcC*.", "nFp:2:") BUILTIN(__builtin_sprintf, "ic*cC*.", "nFP:1:") +BUILTIN(__builtin_vfprintf, "iP*RcC*Ra", "nFP:1:") BUILTIN(__builtin_vsnprintf, "ic*zcC*a", "nFP:2:") BUILTIN(__builtin_vsprintf, "ic*cC*a", "nFP:1:") +BUILTIN(__builtin_fscanf, "iP*RcC*R.", "Fs:1:") +BUILTIN(__builtin_scanf, "icC*R.", "Fs:0:") +BUILTIN(__builtin_sscanf, "icC*RcC*R.", "Fs:1:") +BUILTIN(__builtin_vfscanf, "iP*RcC*Ra", "FS:1:") +BUILTIN(__builtin_vscanf, "icC*Ra", "FS:0:") +BUILTIN(__builtin_vsscanf, "icC*RcC*Ra", "FS:1:") BUILTIN(__builtin_thread_pointer, "v*", "nc") BUILTIN(__builtin_launder, "v*v*", "nt") LANGBUILTIN(__builtin_is_constant_evaluated, "b", "n", CXX_LANG) @@ -1634,6 +1641,7 @@ LANGBUILTIN(__builtin_coro_done, "bv*", "n", COR_LANG) LANGBUILTIN(__builtin_coro_promise, "v*v*IiIb", "n", COR_LANG) LANGBUILTIN(__builtin_coro_size, "z", "n", COR_LANG) +LANGBUILTIN(__builtin_coro_align, "z", "n", COR_LANG) LANGBUILTIN(__builtin_coro_frame, "v*", "n", COR_LANG) LANGBUILTIN(__builtin_coro_noop, "v*", "n", COR_LANG) LANGBUILTIN(__builtin_coro_free, "v*v*", "n", COR_LANG) diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index ef7957979dcc..75b1502e11c7 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -482,6 +482,16 @@ ENUM_CODEGENOPT(ZeroCallUsedRegs, llvm::ZeroCallUsedRegs::ZeroCallUsedRegsKind, /// Whether to use opaque pointers. CODEGENOPT(OpaquePointers, 1, 0) +/// ClangIR specific (internal): limits recursion depth for buildDeferred() +/// calls. This helps incremental progress while building large C++ TUs, once +/// CIRGen is mature we should probably remove it. +VALUE_CODEGENOPT(ClangIRBuildDeferredThreshold, 32, 500) + +/// ClangIR specific (internal): Only build deferred functions not coming from +/// system headers. This helps incremental progress while building large C++ +/// TUs, once CIRGen is mature we should probably remove it. +CODEGENOPT(ClangIRSkipFunctionsFromSystemHeaders, 1, 0) + #undef CODEGENOPT #undef ENUM_CODEGENOPT #undef VALUE_CODEGENOPT diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def index a5bfeb9bb839..db3a81192120 100644 --- a/clang/include/clang/Basic/LangOptions.def +++ b/clang/include/clang/Basic/LangOptions.def @@ -457,6 +457,8 @@ VALUE_LANGOPT(FuchsiaAPILevel, 32, 0, "Fuchsia API level") // on large _BitInts. BENIGN_VALUE_LANGOPT(MaxBitIntWidth, 32, 128, "Maximum width of a _BitInt") +LANGOPT(CIRWarning, 1, 0, "Use CIR for analysis based warning") + #undef LANGOPT #undef COMPATIBLE_LANGOPT #undef BENIGN_LANGOPT diff --git a/clang/include/clang/Basic/LangStandard.h b/clang/include/clang/Basic/LangStandard.h index 1e0ce4dbb0c8..dd831c89bed8 100644 --- a/clang/include/clang/Basic/LangStandard.h +++ b/clang/include/clang/Basic/LangStandard.h @@ -44,6 +44,7 @@ enum class Language : uint8_t { #if ENABLE_BSC BSC, #endif + CIR ///@} }; diff --git a/clang/include/clang/CIR/CIRGenerator.h b/clang/include/clang/CIR/CIRGenerator.h new file mode 100644 index 000000000000..2dedb3b66385 --- /dev/null +++ b/clang/include/clang/CIR/CIRGenerator.h @@ -0,0 +1,107 @@ +//===- CIRGenerator.h - CIR Generation from Clang AST ---------------------===// +// +// 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 declares a simple interface to perform CIR generation from Clang +// AST +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_CIRGENERATOR_H_ +#define CLANG_CIRGENERATOR_H_ + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/Decl.h" +#include "clang/Basic/CodeGenOptions.h" + +#include "llvm/ADT/IntrusiveRefCntPtr.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/VirtualFileSystem.h" + +#include + +namespace mlir { +class MLIRContext; +class ModuleOp; +class OwningModuleRef; +} // namespace mlir + +namespace clang { +class ASTContext; +class DeclGroupRef; +class FunctionDecl; +} // namespace clang + +namespace cir { +class CIRGenModule; +class CIRGenTypes; + +class CIRGenerator : public clang::ASTConsumer { + virtual void anchor(); + clang::DiagnosticsEngine &Diags; + clang::ASTContext *astCtx; + llvm::IntrusiveRefCntPtr + fs; // Only used for debug info. + + const clang::CodeGenOptions codeGenOpts; // Intentionally copied in. + + unsigned HandlingTopLevelDecls; + + /// Use this when emitting decls to block re-entrant decl emission. It will + /// emit all deferred decls on scope exit. Set EmitDeferred to false if decl + /// emission must be deferred longer, like at the end of a tag definition. + struct HandlingTopLevelDeclRAII { + CIRGenerator &Self; + bool EmitDeferred; + HandlingTopLevelDeclRAII(CIRGenerator &Self, bool EmitDeferred = true) + : Self{Self}, EmitDeferred{EmitDeferred} { + ++Self.HandlingTopLevelDecls; + } + ~HandlingTopLevelDeclRAII() { + unsigned Level = --Self.HandlingTopLevelDecls; + if (Level == 0 && EmitDeferred) + Self.buildDeferredDecls(); + } + }; + +protected: + std::unique_ptr mlirCtx; + std::unique_ptr CGM; + +private: + llvm::SmallVector DeferredInlineMemberFuncDefs; + +public: + CIRGenerator(clang::DiagnosticsEngine &diags, + llvm::IntrusiveRefCntPtr FS, + const clang::CodeGenOptions &CGO); + ~CIRGenerator(); + void Initialize(clang::ASTContext &Context) override; + bool EmitFunction(const clang::FunctionDecl *FD); + + bool HandleTopLevelDecl(clang::DeclGroupRef D) override; + void HandleTranslationUnit(clang::ASTContext &Ctx) override; + void HandleInlineFunctionDefinition(clang::FunctionDecl *D) override; + void HandleTagDeclDefinition(clang::TagDecl *D) override; + void HandleTagDeclRequiredDefinition(const clang::TagDecl *D) override; + void HandleCXXStaticMemberVarInstantiation(clang::VarDecl *D) override; + void CompleteTentativeDefinition(clang::VarDecl *D) override; + + mlir::ModuleOp getModule(); + std::unique_ptr takeContext() { + return std::move(mlirCtx); + }; + + bool verifyModule(); + + void buildDeferredDecls(); + void buildDefaultMethods(); +}; + +} // namespace cir + +#endif // CLANG_CIRGENERATOR_H_ diff --git a/clang/include/clang/CIR/CIRToCIRPasses.h b/clang/include/clang/CIR/CIRToCIRPasses.h new file mode 100644 index 000000000000..06d928e5cf15 --- /dev/null +++ b/clang/include/clang/CIR/CIRToCIRPasses.h @@ -0,0 +1,39 @@ +//====- CIRToCIRPasses.h- Lowering from CIR to LLVM -----------------------===// +// +// 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 declares an interface for converting CIR modules to LLVM IR. +// +//===----------------------------------------------------------------------===// +#ifndef CLANG_CIR_CIRTOCIRPASSES_H +#define CLANG_CIR_CIRTOCIRPASSES_H + +#include "mlir/Pass/Pass.h" + +#include + +namespace clang { +class ASTContext; +} + +namespace mlir { +class MLIRContext; +class ModuleOp; +} // namespace mlir + +namespace cir { + +// Run set of cleanup/prepare/etc passes CIR <-> CIR. +mlir::LogicalResult runCIRToCIRPasses(mlir::ModuleOp theModule, + mlir::MLIRContext *mlirCtx, + clang::ASTContext &astCtx, + bool enableVerifier, bool enableLifetime, + llvm::StringRef lifetimeOpts, + bool &passOptParsingFailure); +} // namespace cir + +#endif // CLANG_CIR_CIRTOCIRPASSES_H_ diff --git a/clang/include/clang/CIR/CMakeLists.txt b/clang/include/clang/CIR/CMakeLists.txt new file mode 100644 index 000000000000..2028af5232c2 --- /dev/null +++ b/clang/include/clang/CIR/CMakeLists.txt @@ -0,0 +1,8 @@ +set(MLIR_MAIN_SRC_DIR ${LLVM_MAIN_SRC_DIR}/../mlir/include ) # --src-root +set(MLIR_INCLUDE_DIR ${LLVM_MAIN_SRC_DIR}/../mlir/include ) # --includedir +set(MLIR_TABLEGEN_OUTPUT_DIR ${CMAKE_BINARY_DIR}/tools/mlir/include) +include_directories(SYSTEM ${MLIR_INCLUDE_DIR}) +include_directories(SYSTEM ${MLIR_TABLEGEN_OUTPUT_DIR}) + +add_subdirectory(Dialect) +add_subdirectory(Interfaces) diff --git a/clang/include/clang/CIR/Dialect/CMakeLists.txt b/clang/include/clang/CIR/Dialect/CMakeLists.txt new file mode 100644 index 000000000000..cd837615e82f --- /dev/null +++ b/clang/include/clang/CIR/Dialect/CMakeLists.txt @@ -0,0 +1,28 @@ +add_custom_target(clang-cir-doc) + +# This replicates part of the add_mlir_doc cmake function from MLIR that cannot +# be used here. This happens because it expects to be run inside MLIR directory +# which is not the case for CIR (and also FIR, both have similar workarounds). +function(add_clang_mlir_doc doc_filename output_file output_directory command) + set(LLVM_TARGET_DEFINITIONS ${doc_filename}.td) + tablegen(MLIR ${output_file}.md ${command} ${ARGN} "-I${MLIR_MAIN_SRC_DIR}" "-I${MLIR_INCLUDE_DIR}") + set(GEN_DOC_FILE ${CLANG_BINARY_DIR}/docs/${output_directory}${output_file}.md) + add_custom_command( + OUTPUT ${GEN_DOC_FILE} + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_CURRENT_BINARY_DIR}/${output_file}.md + ${GEN_DOC_FILE} + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${output_file}.md) + add_custom_target(${output_file}DocGen DEPENDS ${GEN_DOC_FILE}) + add_dependencies(clang-cir-doc ${output_file}DocGen) +endfunction() + +add_subdirectory(IR) + +set(LLVM_TARGET_DEFINITIONS Passes.td) +mlir_tablegen(Passes.h.inc -gen-pass-decls -name CIR) +mlir_tablegen(Passes.capi.h.inc -gen-pass-capi-header --prefix CIR) +mlir_tablegen(Passes.capi.cpp.inc -gen-pass-capi-impl --prefix CIR) +add_public_tablegen_target(MLIRCIRPassIncGen) + +add_clang_mlir_doc(Passes CIRPasses ./ -gen-pass-doc) diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrDefs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrDefs.td new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h new file mode 100644 index 000000000000..d72fc13ba60d --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.h @@ -0,0 +1,46 @@ +//===- CIRAttrs.h - MLIR CIR Attrs ------------------------------*- C++ -*-===// +// +// 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 declares the attributes in the CIR dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_CIR_IR_CIRATTRS_H_ +#define MLIR_DIALECT_CIR_IR_CIRATTRS_H_ + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributeInterfaces.h" + +#include "llvm/ADT/SmallVector.h" + +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" + +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" + +//===----------------------------------------------------------------------===// +// CIR Dialect Attrs +//===----------------------------------------------------------------------===// + +namespace clang { +class FunctionDecl; +class VarDecl; +class RecordDecl; +} // namespace clang + +namespace mlir { +namespace cir { +class ArrayType; +class StructType; +class BoolType; +} // namespace cir +} // namespace mlir + +#define GET_ATTRDEF_CLASSES +#include "clang/CIR/Dialect/IR/CIROpsAttributes.h.inc" + +#endif // MLIR_DIALECT_CIR_IR_CIRATTRS_H_ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td new file mode 100644 index 000000000000..c3e551f08632 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -0,0 +1,524 @@ +//===- CIRAttrs.td - CIR dialect types ---------------------*- tablegen -*-===// +// +// 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 declares the CIR dialect attributes. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_CIR_DIALECT_CIR_ATTRS +#define MLIR_CIR_DIALECT_CIR_ATTRS + +include "mlir/IR/BuiltinAttributeInterfaces.td" +include "mlir/IR/EnumAttr.td" + +include "clang/CIR/Dialect/IR/CIRDialect.td" + +include "clang/CIR/Interfaces/ASTAttrInterfaces.td" + +//===----------------------------------------------------------------------===// +// CIR Attrs +//===----------------------------------------------------------------------===// + +class CIR_Attr traits = []> + : AttrDef { + let mnemonic = attrMnemonic; +} + +class CIRUnitAttr traits = []> + : CIR_Attr { + let returnType = "bool"; + let defaultValue = "false"; + let valueType = NoneType; + let isOptional = 1; +} + +//===----------------------------------------------------------------------===// +// LangAttr +//===----------------------------------------------------------------------===// + +def C : I32EnumAttrCase<"C", 1, "c">; +def CXX : I32EnumAttrCase<"CXX", 2, "cxx">; + +def SourceLanguage : I32EnumAttr<"SourceLanguage", "Source language", [ + C, CXX +]> { + let cppNamespace = "::mlir::cir"; + let genSpecializedAttr = 0; +} + +def LangAttr : CIR_Attr<"Lang", "lang"> { + let summary = "Module source language"; + let parameters = (ins SourceLanguage:$lang); + let description = [{ + Represents the source language used to generate the module. + + Example: + ``` + // Module compiled from C. + module attributes {cir.lang = cir.lang} {} + // Module compiled from C++. + module attributes {cir.lang = cir.lang} {} + ``` + }]; + let hasCustomAssemblyFormat = 1; + let extraClassDeclaration = [{ + bool isC() const { return getLang() == SourceLanguage::C; }; + bool isCXX() const { return getLang() == SourceLanguage::CXX; }; + }]; +} + +//===----------------------------------------------------------------------===// +// BoolAttr +//===----------------------------------------------------------------------===// + +def CIR_BoolAttr : CIR_Attr<"Bool", "bool", [TypedAttrInterface]> { + let summary = "Represent true/false for !cir.bool types"; + let description = [{ + The BoolAttr represents a 'true' or 'false' value. + }]; + + let parameters = (ins AttributeSelfTypeParameter< + "", "mlir::cir::BoolType">:$type, + "bool":$value); + + let assemblyFormat = [{ + `<` $value `>` + }]; +} + +//===----------------------------------------------------------------------===// +// 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 = [{}]; +} + +//===----------------------------------------------------------------------===// +// ConstArrayAttr +//===----------------------------------------------------------------------===// + +def ConstArrayAttr : CIR_Attr<"ConstArray", "const_array", [TypedAttrInterface]> { + let summary = "A constant array from ArrayAttr or StringRefAttr"; + let description = [{ + An CIR array attribute is an array of literals of the specified attr types. + }]; + + let parameters = (ins AttributeSelfTypeParameter<"">:$type, + "Attribute":$elts); + + // Define a custom builder for the type; that removes the need to pass + // in an MLIRContext instance, as it can be infered from the `type`. + let builders = [ + AttrBuilderWithInferredContext<(ins "mlir::cir::ArrayType":$type, + "Attribute":$elts), [{ + return $_get(type.getContext(), type, elts); + }]> + ]; + + // Printing and parsing available in CIRDialect.cpp + let hasCustomAssemblyFormat = 1; + + // Enable verifier. + let genVerifyDecl = 1; +} + +//===----------------------------------------------------------------------===// +// ConstStructAttr +//===----------------------------------------------------------------------===// + +def ConstStructAttr : CIR_Attr<"ConstStruct", "const_struct", + [TypedAttrInterface]> { + let summary = "Represents a constant struct"; + let description = [{ + Effectively supports "struct-like" constants. It's must be built from + an `mlir::ArrayAttr `instance where each elements is a typed attribute + (`mlir::TypedAttribute`). + + Example: + ``` + cir.global external @rgb2 = #cir.const_struct<{0 : i8, + 5 : i64, #cir.null : !cir.ptr + }> : !cir.struct<"", i8, i64, !cir.ptr> + ``` + }]; + + let parameters = (ins AttributeSelfTypeParameter<"">:$type, + "ArrayAttr":$members); + + let builders = [ + AttrBuilderWithInferredContext<(ins "mlir::cir::StructType":$type, + "ArrayAttr":$members), [{ + return $_get(type.getContext(), type, members); + }]> + ]; + + let assemblyFormat = [{ + `<` custom($members) `>` + }]; + + let genVerifyDecl = 1; +} + +//===----------------------------------------------------------------------===// +// IntegerAttr +//===----------------------------------------------------------------------===// + +def IntAttr : CIR_Attr<"Int", "int", [TypedAttrInterface]> { + let summary = "An Attribute containing a integer value"; + let description = [{ + An integer attribute is a literal attribute that represents an integral + value of the specified integer type. + }]; + let parameters = (ins AttributeSelfTypeParameter<"">:$type, "APInt":$value); + let builders = [ + AttrBuilderWithInferredContext<(ins "Type":$type, + "const APInt &":$value), [{ + return $_get(type.getContext(), type, value); + }]>, + AttrBuilderWithInferredContext<(ins "Type":$type, "int64_t":$value), [{ + IntType intType = type.cast(); + mlir::APInt apValue(intType.getWidth(), value, intType.isSigned()); + return $_get(intType.getContext(), intType, apValue); + }]>, + ]; + let extraClassDeclaration = [{ + int64_t getSInt() const { return getValue().getSExtValue(); } + uint64_t getUInt() const { return getValue().getZExtValue(); } + bool isNullValue() const { return getValue() == 0; } + }]; + let genVerifyDecl = 1; + let hasCustomAssemblyFormat = 1; +} + +//===----------------------------------------------------------------------===// +// ConstPointerAttr +//===----------------------------------------------------------------------===// + +def ConstPtrAttr : CIR_Attr<"ConstPtr", "ptr", [TypedAttrInterface]> { + let summary = "Holds a constant pointer value"; + let parameters = (ins AttributeSelfTypeParameter<"">:$type, "uint64_t":$value); + let description = [{ + A pointer attribute is a literal attribute that represents an integral + value of a pointer type. + }]; + let builders = [ + AttrBuilderWithInferredContext<(ins "Type":$type, "uint64_t":$value), [{ + return $_get(type.getContext(), type, value); + }]>, + ]; + let extraClassDeclaration = [{ + bool isNullValue() const { return getValue() == 0; } + }]; + let hasCustomAssemblyFormat = 1; +} + +//===----------------------------------------------------------------------===// +// SignedOverflowBehaviorAttr +//===----------------------------------------------------------------------===// + +def SignedOverflowBehaviorAttr : AttrDef { + let mnemonic = "signed_overflow_behavior"; + let parameters = (ins + "sob::SignedOverflowBehavior":$behavior + ); + let hasCustomAssemblyFormat = 1; +} + +//===----------------------------------------------------------------------===// +// GlobalViewAttr +//===----------------------------------------------------------------------===// + +def GlobalViewAttr : CIR_Attr<"GlobalView", "global_view", [TypedAttrInterface]> { + let summary = "Provides constant access to a global address"; + let description = [{ + Get constant address of global `symbol` and optionally apply offsets to + access existing subelements. It provides a way to access globals from other + global and always produces a pointer. + + The type of the input symbol can be different from `#cir.global_view` + output type, since a given view of the global might require a static + cast for initializing other globals. + + A list of indices can be optionally passed and each element subsequently + indexes underlying types. For `symbol` types like `!cir.array` + and `!cir.struct`, it leads to the constant address of sub-elements, while + for `!cir.ptr`, an offset is applied. The first index is relative to the + original symbol type, not the produced one. + + Example: + + ``` + cir.global external @s = @".str2": !cir.ptr + cir.global external @x = #cir.global_view<@s> : !cir.ptr + + cir.global external @rgb = #cir.const_array<[0 : i8, -23 : i8, 33 : i8] : !cir.array> + cir.global external @elt_ptr = #cir.global_view<@rgb, [1]> : !cir.ptr + cir.global external @table_of_ptrs = #cir.const_array<[#cir.global_view<@rgb, [1]> : !cir.ptr] : !cir.array x 1>> + ``` + }]; + + let parameters = (ins AttributeSelfTypeParameter<"">:$type, + "FlatSymbolRefAttr":$symbol, + OptionalParameter<"ArrayAttr">:$indices); + + let builders = [ + AttrBuilderWithInferredContext<(ins "Type":$type, + "FlatSymbolRefAttr":$symbol, + CArg<"ArrayAttr", "{}">:$indices), [{ + return $_get(type.getContext(), type, symbol, indices); + }]> + ]; + + // let genVerifyDecl = 1; + let assemblyFormat = [{ + `<` + $symbol + (`,` $indices^)? + `>` + }]; +} + +//===----------------------------------------------------------------------===// +// TypeInfoAttr +//===----------------------------------------------------------------------===// + +def TypeInfoAttr : CIR_Attr<"TypeInfo", "typeinfo", [TypedAttrInterface]> { + let summary = "Represents a typeinfo used for RTTI"; + let description = [{ + The typeinfo data for a given class is stored into an ArrayAttr. The + layout is determined by the C++ ABI used (clang only implements + itanium on CIRGen). + + The verifier enforces that the output type is always a `!cir.struct`, + and that the ArrayAttr element types match the equivalent member type + for the resulting struct, i.e, a GlobalViewAttr for symbol reference or + an IntAttr for flags. + + Example: + + ``` + cir.global "private" external @_ZTVN10__cxxabiv120__si_class_type_infoE : !cir.ptr + + cir.global external @type_info_B = #cir.typeinfo<< + {#cir.global_view<@_ZTVN10__cxxabiv120__si_class_type_infoE, [2]> : !cir.ptr} + >> : !cir.struct<"", !cir.ptr> + ``` + }]; + + let parameters = (ins AttributeSelfTypeParameter<"">:$type, + "mlir::ArrayAttr":$data); + + let builders = [ + AttrBuilderWithInferredContext<(ins "Type":$type, + "mlir::ArrayAttr":$data), [{ + return $_get(type.getContext(), type, data); + }]> + ]; + + // Checks struct element types should match the array for every equivalent + // element type. + let genVerifyDecl = 1; + let assemblyFormat = [{ + `<` custom($data) `>` + }]; +} + +//===----------------------------------------------------------------------===// +// VTableAttr +//===----------------------------------------------------------------------===// + +def VTableAttr : CIR_Attr<"VTable", "vtable", [TypedAttrInterface]> { + let summary = "Represents a C++ vtable"; + let description = [{ + Wraps a #cir.const_struct containing vtable data. + + Example: + ``` + cir.global linkonce_odr @_ZTV1B = #cir.vtable<< + {#cir.const_array<[#cir.null : !cir.ptr, + #cir.global_view<@_ZTI1B> : !cir.ptr, + #cir.global_view<@_ZN1BD1Ev> : !cir.ptr, + #cir.global_view<@_ZN1BD0Ev> : !cir.ptr, + #cir.global_view<@_ZNK1A5quackEv> : !cir.ptr]> + : !cir.array x 5>}>> + : !cir.struct<"", !cir.array x 5>> + ``` + }]; + + // `vtable_data` is const struct with one element, containing an array of + // vtable information. + let parameters = (ins AttributeSelfTypeParameter<"">:$type, + "ArrayAttr":$vtable_data); + + let builders = [ + AttrBuilderWithInferredContext<(ins "Type":$type, + "ArrayAttr":$vtable_data), [{ + return $_get(type.getContext(), type, vtable_data); + }]> + ]; + + let genVerifyDecl = 1; + let assemblyFormat = [{ + `<` custom($vtable_data) `>` + }]; +} + +//===----------------------------------------------------------------------===// +// AST Wrappers +//===----------------------------------------------------------------------===// + +class ASTDecl traits = []> + : CIR_Attr { + string clang_name = !strconcat("const clang::", name, " *"); + + let summary = !strconcat("Wraps a '", clang_name, "' AST node."); + let description = [{ + Operations optionally refer to this node, they could be available depending + on the CIR lowering stage. Whether it's attached to the appropriated + CIR operation is delegated to the operation verifier. + + This always implies a non-null AST reference (verified). + }]; + let parameters = (ins clang_name:$astDecl); + + // Printing and parsing available in CIRDialect.cpp + let hasCustomAssemblyFormat = 1; + + // Enable verifier. + let genVerifyDecl = 1; + + let extraClassDefinition = [{ + ::mlir::Attribute $cppClass::parse(::mlir::AsmParser &parser, + ::mlir::Type type) { + // We cannot really parse anything AST related at this point + // since we have no serialization/JSON story. + return $cppClass::get(parser.getContext(), nullptr); + } + + void $cppClass::print(::mlir::AsmPrinter &printer) const { + // Nothing to print besides the mnemonics. + } + + LogicalResult $cppClass::verify( + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + }] # clang_name # [{ decl) { + return success(); + } + }]; +} + +def ASTDeclAttr : ASTDecl<"Decl", "decl", [ASTDeclInterface]>; + +def ASTFunctionDeclAttr : ASTDecl<"FunctionDecl", "function.decl", + [ASTFunctionDeclInterface]>; + +def ASTCXXMethodDeclAttr : ASTDecl<"CXXMethodDecl", "cxxmethod.decl", + [ASTCXXMethodDeclInterface]>; + +def ASTCXXConstructorDeclAttr : ASTDecl<"CXXConstructorDecl", + "cxxconstructor.decl", [ASTCXXConstructorDeclInterface]>; + +def ASTCXXConversionDeclAttr : ASTDecl<"CXXConversionDecl", + "cxxconversion.decl", [ASTCXXConversionDeclInterface]>; + +def ASTCXXDestructorDeclAttr : ASTDecl<"CXXDestructorDecl", + "cxxdestructor.decl", [ASTCXXDestructorDeclInterface]>; + +def ASTVarDeclAttr : ASTDecl<"VarDecl", "var.decl", + [ASTVarDeclInterface]>; + +def ASTTypeDeclAttr: ASTDecl<"TypeDecl", "type.decl", + [ASTTypeDeclInterface]>; + +def ASTTagDeclAttr : ASTDecl<"TagDecl", "tag.decl", + [ASTTagDeclInterface]>; + +def ASTRecordDeclAttr : ASTDecl<"RecordDecl", "record.decl", + [ASTRecordDeclInterface]>; + +//===----------------------------------------------------------------------===// +// ExtraFuncAttr +//===----------------------------------------------------------------------===// + +def ExtraFuncAttr : CIR_Attr<"ExtraFuncAttributes", "extra"> { + let summary = "Represents aggregated attributes for a function"; + let description = [{ + This is a wrapper of dictionary attrbiute that contains extra attributes of + a function. + }]; + + let parameters = (ins "DictionaryAttr":$elements); + + let assemblyFormat = [{ `(` $elements `)` }]; + + // Printing and parsing also available in CIRDialect.cpp +} + + +def NoInline : I32EnumAttrCase<"NoInline", 1, "no">; +def AlwaysInline : I32EnumAttrCase<"AlwaysInline", 2, "always">; +def InlineHint : I32EnumAttrCase<"InlineHint", 3, "hint">; + +def InlineKind : I32EnumAttr<"InlineKind", "inlineKind", [ + NoInline, AlwaysInline, InlineHint +]> { + let cppNamespace = "::mlir::cir"; +} + +def InlineAttr : CIR_Attr<"Inline", "inline"> { + let summary = "Inline attribute"; + let description = [{ + Inline attributes represents user directives. + }]; + + let parameters = (ins "InlineKind":$value); + + let assemblyFormat = [{ + `<` $value `>` + }]; + + let extraClassDeclaration = [{ + bool isNoInline() const { return getValue() == InlineKind::NoInline; }; + bool isAlwaysInline() const { return getValue() == InlineKind::AlwaysInline; }; + bool isInlineHint() const { return getValue() == InlineKind::InlineHint; }; + }]; +} + +def OptNoneAttr : CIRUnitAttr<"OptNone", "optnone"> { + let storageType = [{ OptNoneAttr }]; +} + +def GlobalCtorAttr : CIR_Attr<"GlobalCtor", "globalCtor"> { + let summary = "Indicates a function is a global constructor."; + let description = [{ + Describing a global constructor with an optional priority. + }]; + let parameters = (ins "StringAttr":$name, + OptionalParameter<"std::optional">:$priority); + let assemblyFormat = [{ + `<` + $name + (`,` $priority^)? + `>` + }]; + let builders = [ + AttrBuilder<(ins "StringRef":$name, + CArg<"std::optional", "{}">:$priority), [{ + return $_get($_ctxt, StringAttr::get($_ctxt, name), priority); + }]> + ]; + let skipDefaultBuilders = 1; +} +#endif // MLIR_CIR_DIALECT_CIR_ATTRS diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.h b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h new file mode 100644 index 000000000000..7756092404af --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.h @@ -0,0 +1,69 @@ +//===- CIRDialect.h - MLIR Dialect for CIR ----------------------*- C++ -*-===// +// +// 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 declares the Target dialect for CIR in MLIR. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_CIR_CIRDIALECT_H_ +#define MLIR_DIALECT_CIR_CIRDIALECT_H_ + +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Dialect.h" +#include "mlir/IR/FunctionInterfaces.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/Interfaces/CallInterfaces.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Interfaces/LoopLikeInterface.h" +#include "mlir/Interfaces/SideEffectInterfaces.h" + +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIROpsDialect.h.inc" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIROpsStructs.h.inc" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" + +namespace mlir { +namespace OpTrait { + +namespace impl { +// These functions are out-of-line implementations of the methods in the +// corresponding trait classes. This avoids them being template +// instantiated/duplicated. +LogicalResult verifySameFirstOperandAndResultType(Operation *op); +} // namespace impl + +/// This class provides verification for ops that are known to have the same +/// first operand and result type. +/// +template +class SameFirstOperandAndResultType + : public TraitBase { +public: + static LogicalResult verifyTrait(Operation *op) { + return impl::verifySameFirstOperandAndResultType(op); + } +}; + +} // namespace OpTrait + +namespace cir { +void buildTerminatedBody(OpBuilder &builder, Location loc); +} // namespace cir + +} // namespace mlir + +#define GET_OP_CLASSES +#include "clang/CIR/Dialect/IR/CIROps.h.inc" + +#endif // MLIR_DIALECT_CIR_CIRDIALECT_H_ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td new file mode 100644 index 000000000000..8f756fa422e5 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td @@ -0,0 +1,46 @@ +//===- CIRTypes.td - CIR dialect types ---------------------*- tablegen -*-===// +// +// 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 declares the CIR dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_CIR_DIALECT_CIR +#define MLIR_CIR_DIALECT_CIR + +include "mlir/IR/OpBase.td" + +def CIR_Dialect : Dialect { + let name = "cir"; + + // A short one-line summary of our dialect. + let summary = "A high-level dialect for analyzing and optimizing Clang " + "supported languages"; + + let cppNamespace = "::mlir::cir"; + + let useDefaultAttributePrinterParser = 0; + let useDefaultTypePrinterParser = 0; + + let extraClassDeclaration = [{ + void registerAttributes(); + void registerTypes(); + + ::mlir::Type parseType(::mlir::DialectAsmParser &parser) const override; + void printType(::mlir::Type type, + ::mlir::DialectAsmPrinter &printer) const override; + + ::mlir::Attribute parseAttribute(::mlir::DialectAsmParser &parser, + ::mlir::Type type) const override; + + void printAttribute(::mlir::Attribute attr, + ::mlir::DialectAsmPrinter &os) const override; + }]; +} + +#endif // MLIR_CIR_DIALECT_CIR diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td new file mode 100644 index 000000000000..710cb9bd6737 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -0,0 +1,2198 @@ +//===-- CIROps.td - CIR dialect definition -----------------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Definition of the CIR dialect +/// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_CIR_DIALECT_CIR_OPS +#define MLIR_CIR_DIALECT_CIR_OPS + +include "clang/CIR/Dialect/IR/CIRDialect.td" +include "clang/CIR/Dialect/IR/CIRTypes.td" +include "clang/CIR/Dialect/IR/CIRAttrs.td" + +include "clang/CIR/Interfaces/ASTAttrInterfaces.td" + +include "mlir/Interfaces/CallInterfaces.td" +include "mlir/Interfaces/ControlFlowInterfaces.td" +include "mlir/Interfaces/LoopLikeInterface.td" +include "mlir/Interfaces/InferTypeOpInterface.td" +include "mlir/Interfaces/SideEffectInterfaces.td" + +include "mlir/IR/BuiltinAttributeInterfaces.td" +include "mlir/IR/EnumAttr.td" +include "mlir/IR/FunctionInterfaces.td" +include "mlir/IR/SymbolInterfaces.td" + +//===----------------------------------------------------------------------===// +// CIR Ops +//===----------------------------------------------------------------------===// + +class CIR_Op traits = []> : + Op; + +//===----------------------------------------------------------------------===// +// CastOp +//===----------------------------------------------------------------------===// + +// The enumaration value isn't in sync with clang. +def CK_IntegralToBoolean : I32EnumAttrCase<"int_to_bool", 1>; +def CK_ArrayToPointerDecay : I32EnumAttrCase<"array_to_ptrdecay", 2>; +def CK_IntegralCast : I32EnumAttrCase<"integral", 3>; +def CK_BitCast : I32EnumAttrCase<"bitcast", 4>; +def CK_FloatingCast : I32EnumAttrCase<"floating", 5>; +def CK_PtrToBoolean : I32EnumAttrCase<"ptr_to_bool", 6>; +def CK_FloatToIntegral : I32EnumAttrCase<"float_to_int", 7>; +def CK_IntegralToPointer : I32EnumAttrCase<"int_to_ptr", 8>; +def CK_PointerToIntegral : I32EnumAttrCase<"ptr_to_int", 9>; +def CK_FloatToBoolean : I32EnumAttrCase<"float_to_bool", 10>; +def CK_BooleanToIntegral : I32EnumAttrCase<"bool_to_int", 11>; +def CK_IntegralToFloat : I32EnumAttrCase<"int_to_float", 12>; + +def CastKind : I32EnumAttr< + "CastKind", + "cast kind", + [CK_IntegralToBoolean, CK_ArrayToPointerDecay, CK_IntegralCast, + CK_BitCast, CK_FloatingCast, CK_PtrToBoolean, CK_FloatToIntegral, + CK_IntegralToPointer, CK_PointerToIntegral, CK_FloatToBoolean, + CK_BooleanToIntegral, CK_IntegralToFloat]> { + let cppNamespace = "::mlir::cir"; +} + +def CastOp : CIR_Op<"cast", [Pure]> { + // FIXME: not all conversions are free of side effects. + let summary = "Conversion between values of different types"; + let description = [{ + Apply C/C++ usual conversions rules between values. Currently supported kinds: + + - `int_to_bool` + - `ptr_to_bool` + - `array_to_ptrdecay` + - `integral` + - `bitcast` + - `floating` + - `float_to_int` + + This is effectively a subset of the rules from + `llvm-project/clang/include/clang/AST/OperationKinds.def`; but note that some + of the conversions aren't implemented in terms of `cir.cast`, `lvalue-to-rvalue` + for instance is modeled as a regular `cir.load`. + + ```mlir + %4 = cir.cast (int_to_bool, %3 : i32), !cir.bool + ... + %x = cir.cast(array_to_ptrdecay, %0 : !cir.ptr>), !cir.ptr + ``` + }]; + + let arguments = (ins CastKind:$kind, AnyType:$src); + let results = (outs AnyType:$result); + + let assemblyFormat = [{ + `(` $kind `,` $src `:` type($src) `)` + `,` type($result) attr-dict + }]; + + // The input and output types should match the cast kind. + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// ObjSizeOp +//===----------------------------------------------------------------------===// + +def SizeInfoTypeMin : I32EnumAttrCase<"min", 0>; +def SizeInfoTypeMax : I32EnumAttrCase<"max", 1>; + +def SizeInfoType : I32EnumAttr< + "SizeInfoType", + "size info type", + [SizeInfoTypeMin, SizeInfoTypeMax]> { + let cppNamespace = "::mlir::cir"; +} + +def ObjSizeOp : CIR_Op<"objsize", [Pure]> { + let summary = "Conversion between values of different types"; + let description = [{ + }]; + + let arguments = (ins CIR_PointerType:$ptr, SizeInfoType:$kind, + UnitAttr:$dynamic); + let results = (outs CIR_IntType:$result); + + let assemblyFormat = [{ + `(` + $ptr `:` type($ptr) `,` + $kind + (`,` `dynamic` $dynamic^)? + `)` + `->` type($result) attr-dict + }]; + + // Nothing to verify that isn't already covered by constraints. + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// PtrDiffOp +//===----------------------------------------------------------------------===// + +def PtrDiffOp : CIR_Op<"ptr_diff", [Pure, SameTypeOperands]> { + + let summary = "Pointer subtraction arithmetic"; + let description = [{ + `cir.ptr_diff` performs a subtraction between two pointer types with the + same element type and produces a `mlir::cir::IntType` result. + + Note that the result considers the pointer size according to the ABI for + the pointee sizes, e.g. the subtraction between two `!cir.ptr` might + yield 1, meaning 8 bytes, whereas for `void` or function type pointees, + yielding 8 means 8 bytes. + + ```mlir + %7 = "cir.ptr_diff"(%0, %1) : !cir.ptr -> !u64i + ``` + }]; + + let results = (outs CIR_IntType:$result); + let arguments = (ins AnyType:$lhs, AnyType:$rhs); + + let assemblyFormat = [{ + `(` $lhs `,` $rhs `)` `:` type($lhs) `->` type($result) attr-dict + }]; + + // Already covered by the traits + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// PtrStrideOp +//===----------------------------------------------------------------------===// + +def SameFirstOperandAndResultType : + NativeOpTrait<"SameFirstOperandAndResultType">; + +def PtrStrideOp : CIR_Op<"ptr_stride", + [Pure, SameFirstOperandAndResultType]> { + let summary = "Pointer access with stride"; + let description = [{ + Given a base pointer as first operand, provides a new pointer after applying + a stride (second operand). + + ```mlir + %3 = cir.const(0 : i32) : i32 + %4 = cir.ptr_stride(%2 : !cir.ptr, %3 : i32), !cir.ptr + ``` + }]; + + let arguments = (ins AnyType:$base, CIR_IntType:$stride); + let results = (outs AnyType:$result); + + let assemblyFormat = [{ + `(` $base `:` type($base) `,` $stride `:` qualified(type($stride)) `)` + `,` type($result) attr-dict + }]; + + let extraClassDeclaration = [{ + // Get type pointed by the base pointer. + mlir::Type getElementTy() { + return getBase().getType().cast().getPointee(); + } + }]; + + // SameFirstOperandAndResultType already checks all we need. + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// ConstantOp +//===----------------------------------------------------------------------===// + +def ConstantOp : CIR_Op<"const", + [ConstantLike, Pure]> { + // FIXME: Use SameOperandsAndResultType or similar and prevent eye bleeding + // type repetition in the assembly form. + + let summary = "Defines a CIR constant"; + let description = [{ + The `cir.const` operation turns a literal into an SSA value. The data is + attached to the operation as an attribute. + + ```mlir + %0 = cir.const(42 : i32) : i32 + %1 = cir.const(4.2 : f32) : f32 + %2 = cir.const(nullptr : !cir.ptr) : !cir.ptr + ``` + }]; + + // The constant operation takes an attribute as the only input. + let arguments = (ins TypedAttrInterface:$value); + + // The constant operation returns a single value of AnyType. + let results = (outs AnyType:$res); + + let assemblyFormat = [{ + `(` custom($value) `)` attr-dict `:` type($res) + }]; + + let hasVerifier = 1; + + let extraClassDeclaration = [{ + bool isNullPtr() { + if (const auto ptrAttr = getValue().dyn_cast()) + return ptrAttr.isNullValue(); + return false; + } + }]; + + let hasFolder = 1; +} + +//===----------------------------------------------------------------------===// +// AllocaOp +//===----------------------------------------------------------------------===// + +class AllocaTypesMatchWith + : PredOpTrait> { + string lhs = lhsArg; + string rhs = rhsArg; + string transformer = transform; +} + +def AllocaOp : CIR_Op<"alloca", [ + AllocaTypesMatchWith<"'allocaType' matches pointee type of 'addr'", + "addr", "allocaType", + "$_self.cast().getPointee()">]> { + let summary = "Defines a scope-local variable"; + let description = [{ + The `cir.alloca` operation defines a scope-local variable. + + The presence `init` attribute indicates that the local variable represented + by this alloca was originally initialized in C/C++ source code. In such + cases, the first use contains the initialization (a cir.store, a cir.call + to a ctor, etc). + + The result type is a pointer to the input's type. + + Example: + + ```mlir + // int count = 3; + %0 = cir.alloca i32, !cir.ptr, ["count", init] {alignment = 4 : i64} + + // int *ptr; + %1 = cir.alloca !cir.ptr, cir.ptr >, ["ptr"] {alignment = 8 : i64} + ... + ``` + }]; + + let arguments = (ins + TypeAttr:$allocaType, + StrAttr:$name, + UnitAttr:$init, + Confined, [IntMinValue<0>]>:$alignment, + OptionalAttr:$ast + ); + + let results = (outs Res]>:$addr); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "Type":$addr, "Type":$allocaType, + "StringRef":$name, + "IntegerAttr":$alignment)> + ]; + + let extraClassDeclaration = [{ + // Whether the alloca input type is a pointer. + bool isPointerType() { return getAllocaType().isa<::mlir::cir::PointerType>(); } + }]; + + // FIXME: we should not be printing `cir.ptr` below, that should come + // from the pointer type directly. + let assemblyFormat = [{ + $allocaType `,` `cir.ptr` type($addr) `,` + `[` $name + (`,` `init` $init^)? + `]` + (`ast` $ast^)? attr-dict + }]; + + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// LoadOp +//===----------------------------------------------------------------------===// + +def LoadOp : CIR_Op<"load", [ + TypesMatchWith<"type of 'result' matches pointee type of 'addr'", + "addr", "result", + "$_self.cast().getPointee()">]> { + + 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. A unit attribute `deref` can be used to + mark the resulting value as used by another operation to dereference + a pointer. + + Example: + + ```mlir + + // Read from local variable, address in %0. + %1 = cir.load %0 : !cir.ptr, i32 + + // Load address from memory at address %0. %3 is used by at least one + // operation that dereferences a pointer. + %3 = cir.load deref %0 : cir.ptr > + ``` + }]; + + let arguments = (ins Arg:$addr, UnitAttr:$isDeref); + let results = (outs AnyType:$result); + + // FIXME: we should not be printing `cir.ptr` below, that should come + // from the pointer type directly. + let assemblyFormat = [{ + (`deref` $isDeref^)? $addr `:` `cir.ptr` type($addr) `,` + type($result) attr-dict + }]; + + // FIXME: add verifier. +} + +//===----------------------------------------------------------------------===// +// StoreOp +//===----------------------------------------------------------------------===// + +def StoreOp : CIR_Op<"store", [ + TypesMatchWith<"type of 'value' matches pointee type of 'addr'", + "addr", "value", + "$_self.cast().getPointee()">]> { + + let summary = "Store value to memory address"; + let description = [{ + `cir.store` stores a value (first operand) to the memory address specified + in the second operand. + + Example: + + ```mlir + // Store a function argument to local storage, address in %0. + cir.store %arg0, %0 : i32, !cir.ptr + ``` + }]; + + let arguments = (ins AnyType:$value, + Arg:$addr); + + // FIXME: we should not be printing `cir.ptr` below, that should come + // from the pointer type directly. + let assemblyFormat = + "$value `,` $addr attr-dict `:` type($value) `,` `cir.ptr` type($addr)"; + + // FIXME: add verifier. +} + +//===----------------------------------------------------------------------===// +// ReturnOp +//===----------------------------------------------------------------------===// + +def ReturnOp : CIR_Op<"return", [HasParent<"FuncOp, ScopeOp, IfOp, SwitchOp, LoopOp">, + Terminator]> { + let summary = "Return from function"; + let description = [{ + The "return" operation represents a return operation within a function. + The operation takes an optional operand and produces no results. + The operand type must match the signature of the function that contains + the operation. + + ```mlir + func @foo() -> i32 { + ... + cir.return %0 : i32 + } + ``` + }]; + + // The return operation takes an optional input operand to return. This + // value must match the return type of the enclosing function. + let arguments = (ins Variadic:$input); + + // The return operation only emits the input in the format if it is present. + let assemblyFormat = "($input^ `:` type($input))? attr-dict "; + + // Allow building a ReturnOp with no return operand. + let builders = [ + OpBuilder<(ins), [{ build($_builder, $_state, std::nullopt); }]> + ]; + + // Provide extra utility definitions on the c++ operation class definition. + let extraClassDeclaration = [{ + bool hasOperand() { return getNumOperands() != 0; } + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// IfOp +//===----------------------------------------------------------------------===// + +def IfOp : CIR_Op<"if", + [DeclareOpInterfaceMethods, + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> { + let summary = "The if-then-else operation"; + let description = [{ + The `cir.if` operation represents an if-then-else construct for + conditionally executing two regions of code. The operand is a `cir.bool` + type. + + Examples: + + ```mlir + cir.if %b { + ... + } else { + ... + } + + cir.if %c { + ... + } + + cir.if %c { + ... + cir.br ^a + ^a: + cir.yield + } + ``` + + `cir.if` defines no values and the 'else' can be omitted. `cir.yield` must + explicitly terminate the region if it has more than one block. + }]; + let arguments = (ins CIR_BoolType:$condition); + let regions = (region AnyRegion:$thenRegion, AnyRegion:$elseRegion); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "Value":$cond, "bool":$withElseRegion, + CArg<"function_ref", + "buildTerminatedBody">:$thenBuilder, + CArg<"function_ref", + "nullptr">:$elseBuilder)> + ]; +} + +//===----------------------------------------------------------------------===// +// TernaryOp +//===----------------------------------------------------------------------===// + +def TernaryOp : CIR_Op<"ternary", + [DeclareOpInterfaceMethods, + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> { + let summary = "The `cond ? a : b` C/C++ ternary operation"; + let description = [{ + The `cir.ternary` operation represents C/C++ ternary, much like a `select` + operation. First argument is a `cir.bool` condition to evaluate, followed + by two regions to execute (true or false). This is different from `cir.if` + since each region is one block sized and the `cir.yield` closing the block + scope should have one argument. + + Example: + + ```mlir + // x = cond ? a : b; + + %x = cir.ternary (%cond, true_region { + ... + cir.yield %a : i32 + }, false_region { + ... + cir.yield %b : i32 + }) -> i32 + ``` + }]; + let arguments = (ins CIR_BoolType:$cond); + let regions = (region SizedRegion<1>:$trueRegion, + SizedRegion<1>:$falseRegion); + let results = (outs Optional:$result); + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "Value":$cond, + "function_ref":$trueBuilder, + "function_ref":$falseBuilder) + > + ]; + + // All constraints already verified elsewhere. + let hasVerifier = 0; + + let assemblyFormat = [{ + `(` $cond `,` + `true` $trueRegion `,` + `false` $falseRegion + `)` `:` functional-type(operands, results) attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// YieldOp +//===----------------------------------------------------------------------===// + +def YieldOpKind_BK : I32EnumAttrCase<"Break", 1, "break">; +def YieldOpKind_FT : I32EnumAttrCase<"Fallthrough", 2, "fallthrough">; +def YieldOpKind_CE : I32EnumAttrCase<"Continue", 3, "continue">; +def YieldOpKind_NS : I32EnumAttrCase<"NoSuspend", 4, "nosuspend">; + +def YieldOpKind : I32EnumAttr< + "YieldOpKind", + "yield kind", + [YieldOpKind_BK, YieldOpKind_FT, YieldOpKind_CE, YieldOpKind_NS]> { + let cppNamespace = "::mlir::cir"; +} + +def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator, + ParentOneOf<["IfOp", "ScopeOp", "SwitchOp", "LoopOp", "AwaitOp", + "TernaryOp", "GlobalOp"]>]> { + let summary = "Terminate CIR regions"; + let description = [{ + The `cir.yield` operation terminates regions on different CIR operations: + `cir.if`, `cir.scope`, `cir.switch`, `cir.loop`, `cir.await`, `cir.ternary` + and `cir.global`. + + Might yield an SSA value and the semantics of how the values are yielded is + defined by the parent operation. + + Optionally, `cir.yield` can be annotated with extra kind specifiers: + - `break`: breaking out of the innermost `cir.switch` / `cir.loop` semantics, + cannot be used if not dominated by these parent operations. + - `fallthrough`: execution falls to the next region in `cir.switch` case list. + Only available inside `cir.switch` regions. + - `continue`: only allowed under `cir.loop`, continue execution to the next + loop step. + - `nosuspend`: specific to the `ready` region inside `cir.await` op, it makes + control-flow to be transfered back to the parent, preventing suspension. + + As a general rule, `cir.yield` must be explicitly used whenever a region has + more than one block and no terminator, or within `cir.switch` regions not + `cir.return` terminated. + + Examples: + ```mlir + cir.if %4 { + ... + cir.yield + } + + cir.switch (%5) [ + case (equal, 3) { + ... + cir.yield fallthrough + }, ... + ] + + cir.loop (cond : {...}, step : {...}) { + ... + cir.yield continue + } + + cir.await(init, ready : { + // Call std::suspend_always::await_ready + %18 = cir.call @_ZNSt14suspend_always11await_readyEv(...) + cir.if %18 { + // yields back to the parent. + cir.yield nosuspend + } + cir.yield // control-flow to the next region for suspension. + }, ...) + + cir.scope { + ... + cir.yield + } + + %x = cir.scope { + ... + cir.yield %val + } + + %y = cir.ternary { + ... + cir.yield %val : i32 + } : i32 + ``` + }]; + + let arguments = (ins OptionalAttr:$kind, + Variadic:$args); + let builders = [ + OpBuilder<(ins), [{ /* nothing to do */ }]>, + OpBuilder<(ins "YieldOpKind":$kind), [{ + mlir::cir::YieldOpKindAttr kattr = mlir::cir::YieldOpKindAttr::get( + $_builder.getContext(), kind); + $_state.addAttribute(getKindAttrName($_state.name), kattr); + }]>, + OpBuilder<(ins "ValueRange":$results), [{ + $_state.addOperands(results); + }]> + ]; + + let assemblyFormat = [{ + ($kind^)? ($args^ `:` type($args))? attr-dict + }]; + + let extraClassDeclaration = [{ + // None of the below + bool isPlain() { + return !getKind(); + } + bool isFallthrough() { + return !isPlain() && *getKind() == YieldOpKind::Fallthrough; + } + bool isBreak() { + return !isPlain() && *getKind() == YieldOpKind::Break; + } + bool isContinue() { + return !isPlain() && *getKind() == YieldOpKind::Continue; + } + bool isNoSuspend() { + return !isPlain() && *getKind() == YieldOpKind::NoSuspend; + } + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// ScopeOp +//===----------------------------------------------------------------------===// + +def ScopeOp : CIR_Op<"scope", [ + DeclareOpInterfaceMethods, + RecursivelySpeculatable, AutomaticAllocationScope, + NoRegionArguments]> { + let summary = "Represents a C/C++ scope"; + let description = [{ + `cir.scope` contains one region and defines a strict "scope" for all new + values produced within its blocks. + + The region can contain an arbitrary number of blocks but usually defaults + to one and can optionally return a value (useful for representing values + coming out of C++ full-expressions) via `cir.yield`: + + + ```mlir + %rvalue = cir.scope { + ... + cir.yield %value + } + ``` + + If `cir.scope` yields no value, the `cir.yield` can be left out, and + will be inserted implicitly. + }]; + + let results = (outs Variadic:$results); + let regions = (region AnyRegion:$scopeRegion); + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; + let skipDefaultBuilders = 1; + + let builders = [ + // Scopes for yielding values. + OpBuilder<(ins + "function_ref":$scopeBuilder)>, + // Scopes without yielding values. + OpBuilder<(ins "function_ref":$scopeBuilder)> + ]; +} + +//===----------------------------------------------------------------------===// +// UnaryOp +//===----------------------------------------------------------------------===// + +def UnaryOpKind_Inc : I32EnumAttrCase<"Inc", 1, "inc">; +def UnaryOpKind_Dec : I32EnumAttrCase<"Dec", 2, "dec">; +def UnaryOpKind_Plus : I32EnumAttrCase<"Plus", 3, "plus">; +def UnaryOpKind_Minus : I32EnumAttrCase<"Minus", 4, "minus">; +def UnaryOpKind_Not : I32EnumAttrCase<"Not", 5, "not">; + +def UnaryOpKind : I32EnumAttr< + "UnaryOpKind", + "unary operation kind", + [UnaryOpKind_Inc, + UnaryOpKind_Dec, + UnaryOpKind_Plus, + UnaryOpKind_Minus, + UnaryOpKind_Not, + ]> { + let cppNamespace = "::mlir::cir"; +} + +// FIXME: Pure won't work when we add overloading. +def UnaryOp : CIR_Op<"unary", [Pure, SameOperandsAndResultType]> { + let summary = "Unary operations"; + let description = [{ + `cir.unary` performs the unary operation according to + the specified opcode kind: [inc, dec, plus, minus, not]. + + Note for inc and dec: the operation corresponds only to the + addition/subtraction, its input is expect to come from a load + and the result to be used by a corresponding store. + + It requires one input operand and has one result, both types + should be the same. + + ```mlir + %7 = cir.unary(inc, %1) : i32 -> i32 + %8 = cir.unary(dec, %2) : i32 -> i32 + ``` + }]; + + let results = (outs AnyType:$result); + let arguments = (ins Arg:$kind, Arg:$input); + + let assemblyFormat = [{ + `(` $kind `,` $input `)` `:` type($input) `,` type($result) attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// BinOp +//===----------------------------------------------------------------------===// + +// FIXME: represent Commutative, Idempotent traits for appropriate binops +def BinOpKind_Mul : I32EnumAttrCase<"Mul", 1, "mul">; +def BinOpKind_Div : I32EnumAttrCase<"Div", 2, "div">; +def BinOpKind_Rem : I32EnumAttrCase<"Rem", 3, "rem">; +def BinOpKind_Add : I32EnumAttrCase<"Add", 4, "add">; +def BinOpKind_Sub : I32EnumAttrCase<"Sub", 5, "sub">; +def BinOpKind_And : I32EnumAttrCase<"And", 8, "and">; +def BinOpKind_Xor : I32EnumAttrCase<"Xor", 9, "xor">; +def BinOpKind_Or : I32EnumAttrCase<"Or", 10, "or">; + +def BinOpKind : I32EnumAttr< + "BinOpKind", + "binary operation (arith and logic) kind", + [BinOpKind_Mul, BinOpKind_Div, BinOpKind_Rem, + BinOpKind_Add, BinOpKind_Sub, + BinOpKind_And, BinOpKind_Xor, + BinOpKind_Or]> { + let cppNamespace = "::mlir::cir"; +} + +// FIXME: Pure won't work when we add overloading. +def BinOp : CIR_Op<"binop", [Pure, + SameTypeOperands, SameOperandsAndResultType]> { + + let summary = "Binary operations (arith and logic)"; + let description = [{ + cir.binop performs the binary operation according to + the specified opcode kind: [mul, div, rem, add, sub, + and, xor, or]. + + It requires two input operands and has one result, all types + should be the same. + + ```mlir + %7 = cir.binop(add, %1, %2) : !s32i + %7 = cir.binop(mul, %1, %2) : !u8i + ``` + }]; + + // TODO: get more accurate than AnyType + let results = (outs AnyType:$result); + let arguments = (ins Arg:$kind, + AnyType:$lhs, AnyType:$rhs); + + let assemblyFormat = [{ + `(` $kind `,` $lhs `,` $rhs `)` `:` type($lhs) attr-dict + }]; + + // Already covered by the traits + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// ShiftOp +//===----------------------------------------------------------------------===// + +def ShiftOp : CIR_Op<"shift", [Pure]> { + let summary = "Shift"; + let description = [{ + Shift `left` or `right`, according to the first operand. Second operand is + the shift target and the third the amount. + + ```mlir + %7 = cir.shift(left, %1 : !u64i, %4 : !s32i) -> !u64i + ``` + }]; + + let results = (outs CIR_IntType:$result); + let arguments = (ins CIR_IntType:$value, CIR_IntType:$amount, + UnitAttr:$isShiftleft); + + let assemblyFormat = [{ + `(` + (`left` $isShiftleft^) : (`right`)? + `,` $value `:` type($value) + `,` $amount `:` type($amount) + `)` `->` type($result) attr-dict + }]; + + // Already covered by the traits + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// CmpOp +//===----------------------------------------------------------------------===// + +def CmpOpKind_LT : I32EnumAttrCase<"lt", 1>; +def CmpOpKind_LE : I32EnumAttrCase<"le", 2>; +def CmpOpKind_GT : I32EnumAttrCase<"gt", 3>; +def CmpOpKind_GE : I32EnumAttrCase<"ge", 4>; +def CmpOpKind_EQ : I32EnumAttrCase<"eq", 5>; +def CmpOpKind_NE : I32EnumAttrCase<"ne", 6>; + +def CmpOpKind : I32EnumAttr< + "CmpOpKind", + "compare operation kind", + [CmpOpKind_LT, CmpOpKind_LE, CmpOpKind_GT, + CmpOpKind_GE, CmpOpKind_EQ, CmpOpKind_NE]> { + let cppNamespace = "::mlir::cir"; +} + +// FIXME: Pure might not work when we add overloading. +def CmpOp : CIR_Op<"cmp", [Pure, SameTypeOperands]> { + + let summary = "Compare values two values and produce a boolean result"; + let description = [{ + `cir.cmp` compares two input operands of the same type and produces a + `cir.bool` result. The kinds of comparison available are: + [lt,gt,ge,eq,ne] + + ```mlir + %7 = cir.cmp(gt, %1, %2) : i32, !cir.bool + ``` + }]; + + // TODO: get more accurate than AnyType + let results = (outs AnyType:$result); + let arguments = (ins Arg:$kind, + AnyType:$lhs, AnyType:$rhs); + + let assemblyFormat = [{ + `(` $kind `,` $lhs `,` $rhs `)` `:` type($lhs) `,` type($result) attr-dict + }]; + + // Already covered by the traits + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// SwitchOp +//===----------------------------------------------------------------------===// + +def CaseOpKind_DT : I32EnumAttrCase<"Default", 1, "default">; +def CaseOpKind_EQ : I32EnumAttrCase<"Equal", 2, "equal">; +def CaseOpKind_AO : I32EnumAttrCase<"Anyof", 3, "anyof">; + +def CaseOpKind : I32EnumAttr< + "CaseOpKind", + "case kind", + [CaseOpKind_DT, CaseOpKind_EQ, CaseOpKind_AO]> { + let cppNamespace = "::mlir::cir"; +} + +def CaseEltValueListAttr : + TypedArrayAttrBase { + let constBuilderCall = ?; +} + +def CaseAttr : AttrDef { + // FIXME: value should probably be optional for more clear "default" + // representation. + let parameters = (ins "ArrayAttr":$value, "CaseOpKindAttr":$kind); + let mnemonic = "case"; + let assemblyFormat = "`<` struct(params) `>`"; +} + + +def CaseArrayAttr : + TypedArrayAttrBase { + let constBuilderCall = ?; +} + +def SwitchOp : CIR_Op<"switch", + [SameVariadicOperandSize, + DeclareOpInterfaceMethods, + RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> { + let summary = "Switch operation"; + let description = [{ + The `cir.switch` operation represents C/C++ switch functionality for + conditionally executing multiple regions of code. The operand to an switch + is an integral condition value. + + A variadic list of "case" attribute operands and regions track the possible + control flow within `cir.switch`. A `case` must be in one of the following forms: + - `equal, `: equality of the second case operand against the + condition. + - `anyof, [constant-list]`: equals to any of the values in a subsequent + following list. + - `default`: any other value. + + Each case region must be explicitly terminated. + + Examples: + + ```mlir + cir.switch (%b : i32) [ + case (equal, 20) { + ... + cir.yield break + }, + case (anyof, [1, 2, 3] : i32) { + ... + cir.return ... + } + case (default) { + ... + cir.yield fallthrough + } + ] + ``` + }]; + + let arguments = (ins CIR_IntType:$condition, + OptionalAttr:$cases); + + let regions = (region VariadicRegion:$regions); + + let hasVerifier = 1; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins "Value":$condition, + "function_ref":$switchBuilder)> + ]; + + let assemblyFormat = [{ + custom( + $regions, $cases, $condition, type($condition) + ) + attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// BrOp +//===----------------------------------------------------------------------===// + +def BrOp : CIR_Op<"br", + [DeclareOpInterfaceMethods, + Pure, Terminator]> { + let summary = "Unconditional branch"; + let description = [{ + The `cir.br` branches unconditionally to a block. Used to represent C/C++ + goto's and general block branching. + + Example: + + ```mlir + ... + cir.br ^bb3 + ^bb3: + cir.return + ``` + }]; + + let builders = [ + OpBuilder<(ins "Block *":$dest, + CArg<"ValueRange", "{}">:$destOperands), [{ + $_state.addSuccessors(dest); + $_state.addOperands(destOperands); + }]> + ]; + + let arguments = (ins Variadic:$destOperands); + let successors = (successor AnySuccessor:$dest); + let assemblyFormat = [{ + $dest (`(` $destOperands^ `:` type($destOperands) `)`)? attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// BrCondOp +//===----------------------------------------------------------------------===// + +def BrCondOp : CIR_Op<"brcond", + [DeclareOpInterfaceMethods, + Pure, Terminator, SameVariadicOperandSize]> { + let summary = "Conditional branch"; + let description = [{ + The `cir.brcond %cond, ^bb0, ^bb1` branches to 'bb0' block in case + %cond (which must be a !cir.bool type) evaluates to true, otherwise + it branches to 'bb1'. + + Example: + + ```mlir + ... + cir.brcond %a, ^bb3, ^bb4 + ^bb3: + cir.return + ^bb4: + cir.yield + ``` + }]; + + let builders = [ + OpBuilder<(ins "Value":$cond, "Block *":$destTrue, "Block *":$destFalse, + CArg<"ValueRange", "{}">:$destOperandsTrue, + CArg<"ValueRange", "{}">:$destOperandsFalse), [{ + $_state.addOperands(cond); + $_state.addSuccessors(destTrue); + $_state.addSuccessors(destFalse); + $_state.addOperands(destOperandsTrue); + $_state.addOperands(destOperandsFalse); + }]> + ]; + + let arguments = (ins CIR_BoolType:$cond, + Variadic:$destOperandsTrue, + Variadic:$destOperandsFalse); + let successors = (successor AnySuccessor:$destTrue, AnySuccessor:$destFalse); + let assemblyFormat = [{ + $cond + $destTrue (`(` $destOperandsTrue^ `:` type($destOperandsTrue) `)`)? + `,` + $destFalse (`(` $destOperandsFalse^ `:` type($destOperandsFalse) `)`)? + attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// LoopOp +//===----------------------------------------------------------------------===// + +def LoopOpKind_For : I32EnumAttrCase<"For", 1, "for">; +def LoopOpKind_While : I32EnumAttrCase<"While", 2, "while">; +def LoopOpKind_DoWhile : I32EnumAttrCase<"DoWhile", 3, "dowhile">; + +def LoopOpKind : I32EnumAttr< + "LoopOpKind", + "Loop kind", + [LoopOpKind_For, LoopOpKind_While, LoopOpKind_DoWhile]> { + let cppNamespace = "::mlir::cir"; +} + +def LoopOp : CIR_Op<"loop", + [DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, + RecursivelySpeculatable, NoRegionArguments]> { + let summary = "Loop"; + let description = [{ + `cir.loop` represents C/C++ loop forms. It defines 3 blocks: + - `cond`: region can contain multiple blocks, terminated by regular + `cir.yield` when control should yield back to the parent, and + `cir.yield continue` when execution continues to another region. + The region destination depends on the loop form specified. + - `step`: region with one block, containing code to compute the + loop step, must be terminated with `cir.yield`. + - `body`: region for the loop's body, can contain an arbitrary + number of blocks. + + The loop form: `for`, `while` and `dowhile` must also be specified and + each implies the loop regions execution order. + + ```mlir + // while (true) { + // i = i + 1; + // } + cir.loop while(cond : { + cir.yield continue + }, step : { + cir.yield + }) { + %3 = cir.load %1 : cir.ptr , i32 + %4 = cir.const(1 : i32) : i32 + %5 = cir.binop(add, %3, %4) : i32 + cir.store %5, %1 : i32, cir.ptr + cir.yield + } + ``` + }]; + + let arguments = (ins Arg:$kind); + let regions = (region AnyRegion:$cond, AnyRegion:$body, + SizedRegion<1>:$step); + + let assemblyFormat = [{ + $kind + `(` + `cond` `:` $cond `,` + `step` `:` $step + `)` + $body + attr-dict + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins + "cir::LoopOpKind":$kind, + CArg<"function_ref", + "nullptr">:$condBuilder, + CArg<"function_ref", + "nullptr">:$bodyBuilder, + CArg<"function_ref", + "nullptr">:$stepBuilder + )> + ]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// GlobalOp +//===----------------------------------------------------------------------===// + +// Linkage types. This is currently a replay of llvm/IR/GlobalValue.h, this is +// currently handy as part of forwarding appropriate linkage types for LLVM +// lowering, specially useful for C++ support. + +// Externally visible function +def Global_ExternalLinkage : + I32EnumAttrCase<"ExternalLinkage", 0, "external">; +// Available for inspection, not emission. +def Global_AvailableExternallyLinkage : + I32EnumAttrCase<"AvailableExternallyLinkage", 1, "available_externally">; +// Keep one copy of function when linking (inline) +def Global_LinkOnceAnyLinkage : + I32EnumAttrCase<"LinkOnceAnyLinkage", 2, "linkonce">; +// Same, but only replaced by something equivalent. +def Global_LinkOnceODRLinkage : + I32EnumAttrCase<"LinkOnceODRLinkage", 3, "linkonce_odr">; +// Keep one copy of named function when linking (weak) +def Global_WeakAnyLinkage : + I32EnumAttrCase<"WeakAnyLinkage", 4, "weak">; +// Same, but only replaced by something equivalent. +def Global_WeakODRLinkage : + I32EnumAttrCase<"WeakODRLinkage", 5, "weak_odr">; +// TODO: should we add something like appending linkage too? +// Special purpose, only applies to global arrays +// def Global_AppendingLinkage : +// I32EnumAttrCase<"AppendingLinkage", 6, "appending">; +// Rename collisions when linking (static functions). +def Global_InternalLinkage : + I32EnumAttrCase<"InternalLinkage", 7, "internal">; +// Like Internal, but omit from symbol table, prefix it with +// "cir_" to prevent clash with MLIR's symbol "private". +def Global_PrivateLinkage : + I32EnumAttrCase<"PrivateLinkage", 8, "cir_private">; +// ExternalWeak linkage description. +def Global_ExternalWeakLinkage : + I32EnumAttrCase<"ExternalWeakLinkage", 9, "extern_weak">; +// Tentative definitions. +def Global_CommonLinkage : + I32EnumAttrCase<"CommonLinkage", 10, "common">; + +/// An enumeration for the kinds of linkage for global values. +def GlobalLinkageKind : I32EnumAttr< + "GlobalLinkageKind", + "Linkage type/kind", + [Global_ExternalLinkage, Global_AvailableExternallyLinkage, + Global_LinkOnceAnyLinkage, Global_LinkOnceODRLinkage, + Global_WeakAnyLinkage, Global_WeakODRLinkage, + Global_InternalLinkage, Global_PrivateLinkage, + Global_ExternalWeakLinkage, Global_CommonLinkage + ]> { + let cppNamespace = "::mlir::cir"; +} + +def SOB_Undefined : I32EnumAttrCase<"undefined", 1>; +def SOB_Defined : I32EnumAttrCase<"defined", 2>; +def SOB_Trapping : I32EnumAttrCase<"trapping", 3>; + +def SignedOverflowBehaviorEnum : I32EnumAttr< + "SignedOverflowBehavior", + "the behavior for signed overflow", + [SOB_Undefined, SOB_Defined, SOB_Trapping]> { + let cppNamespace = "::mlir::cir::sob"; +} + + +def GlobalOp : CIR_Op<"global", [Symbol, DeclareOpInterfaceMethods, NoRegionArguments]> { + let summary = "Declares or defines a global variable"; + let description = [{ + The `cir.global` operation declares or defines a named global variable. + + The backing memory for the variable is allocated statically and is + described by the type of the variable. + + The operation is a declaration if no `inital_value` is + specified, else it is a definition. + + The global variable can also be marked constant using the + `constant` unit attribute. Writing to such constant global variables is + undefined. + + The `linkage` tracks C/C++ linkage types, currently very similar to LLVM's. + Symbol visibility in `sym_visibility` is defined in terms of MLIR's visibility + and verified to be in accordance to `linkage`. + + Example: + + ```mlir + // Public and constant variable with initial value. + cir.global public constant @c : i32 = 4; + ``` + }]; + + // Note that both sym_name and sym_visibility are tied to Symbol trait. + // TODO: sym_visibility can possibly be represented by implementing the + // necessary Symbol's interface in terms of linkage instead. + let arguments = (ins SymbolNameAttr:$sym_name, + OptionalAttr:$sym_visibility, + TypeAttr:$sym_type, + Arg:$linkage, + // Note this can also be a FlatSymbolRefAttr + OptionalAttr:$initial_value, + UnitAttr:$constant, + OptionalAttr:$alignment, + OptionalAttr:$ast + ); + let regions = (region AnyRegion:$ctorRegion, AnyRegion:$dtorRegion); + let assemblyFormat = [{ + ($sym_visibility^)? + (`constant` $constant^)? + $linkage + $sym_name + custom($sym_type, $initial_value, $ctorRegion, $dtorRegion) + attr-dict + }]; + + let extraClassDeclaration = [{ + bool isDeclaration() { + return !getInitialValue() && getCtorRegion().empty() && getDtorRegion().empty(); + } + bool hasInitializer() { return !isDeclaration(); } + bool hasAvailableExternallyLinkage() { + return mlir::cir::isAvailableExternallyLinkage(getLinkage()); + } + bool isDeclarationForLinker() { + if (hasAvailableExternallyLinkage()) + return true; + + return isDeclaration(); + } + + /// Whether the definition of this global may be replaced at link time. + bool isWeakForLinker() { return cir::isWeakForLinker(getLinkage()); } + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins + // MLIR's default visibility is public. + "StringRef":$sym_name, + "Type":$sym_type, + CArg<"bool", "false">:$isConstant, + // CIR defaults to external linkage. + CArg<"cir::GlobalLinkageKind", + "cir::GlobalLinkageKind::ExternalLinkage">:$linkage, + CArg<"function_ref", + "nullptr">:$ctorBuilder, + CArg<"function_ref", + "nullptr">:$dtorBuilder)> + ]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// GetGlobalOp +//===----------------------------------------------------------------------===// + +def GetGlobalOp : CIR_Op<"get_global", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Get the address of a global variable"; + let description = [{ + The `cir.get_global` operation retrieves the address pointing to a + named global variable. If the global variable is marked constant, writing + to the resulting address (such as through a `cir.store` operation) is + undefined. Resulting type must always be a `!cir.ptr<...>` type. + + Example: + + ```mlir + %x = cir.get_global @foo : !cir.ptr + ``` + }]; + + let arguments = (ins FlatSymbolRefAttr:$name); + let results = (outs Res:$addr); + + // FIXME: we should not be printing `cir.ptr` below, that should come + // from the pointer type directly. + let assemblyFormat = "$name `:` `cir.ptr` type($addr) attr-dict"; + + // `GetGlobalOp` is fully verified by its traits. + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// VTableAddrPointOp +//===----------------------------------------------------------------------===// + +def VTableAddrPointOp : CIR_Op<"vtable.address_point", + [Pure, DeclareOpInterfaceMethods]> { + let summary = "Get the vtable (global variable) address point"; + let description = [{ + The `vtable.address_point` operation retrieves the "effective" address + (address point) of a C++ virtual table. An object internal `__vptr` + gets initializated on top of the value returned by this operation. + + `vtable_index` provides the appropriate vtable within the vtable group + (as specified by Itanium ABI), and `addr_point_index` the actual address + point within that vtable. + + The return type is always a `!cir.ptr i32>>`. + + Example: + ```mlir + cir.global linkonce_odr @_ZTV1B = ... + ... + %3 = cir.vtable.address_point(@_ZTV1B, vtable_index = 0, address_point_index = 2) : cir.ptr i32>> + ``` + }]; + + let arguments = (ins OptionalAttr:$name, + Optional:$sym_addr, + I32Attr:$vtable_index, + I32Attr:$address_point_index); + let results = (outs Res:$addr); + + // FIXME: we should not be printing `cir.ptr` below, that should come + // from the pointer type directly. + let assemblyFormat = [{ + `(` + ($name^)? + ($sym_addr^ `:` type($sym_addr))? + `,` + `vtable_index` `=` $vtable_index `,` + `address_point_index` `=` $address_point_index + `)` + `:` `cir.ptr` type($addr) attr-dict + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// GetMemberOp +//===----------------------------------------------------------------------===// + +def GetMemberOp : CIR_Op<"get_member"> { + let summary = "Get the address of a member of a struct"; + let description = [{ + The `cir.get_member` operation gets the address of a particular named + member from the input record. + + It expects a pointer to the base record as well as the name of the member + and its field index. + + Example: + ```mlir + // Suppose we have a struct with multiple members. + !s32i = !cir.int + !s8i = !cir.int + !struct_ty = !cir.struct<"struct.Bar" {!s32i, !s8i}> + + // Get the address of the member at index 1. + %1 = cir.get_member %0[1] {name = "i"} : (!cir.ptr) -> !cir.ptr + ``` + }]; + + let arguments = (ins + Arg:$addr, + StrAttr:$name, + IndexAttr:$index_attr); + + let results = (outs Res:$result); + + let assemblyFormat = [{ + $addr `[` $index_attr `]` attr-dict + `:` qualified(type($addr)) `->` qualified(type($result)) + }]; + + let builders = [ + OpBuilder<(ins "Type":$type, + "Value":$value, + "llvm::StringRef":$name, + "unsigned":$index), + [{ + mlir::APInt fieldIdx(64, index); + build($_builder, $_state, type, value, name, fieldIdx); + }]> + ]; + + let extraClassDeclaration = [{ + /// Return the index of the struct member being accessed. + uint64_t getIndex() { return getIndexAttr().getZExtValue(); } + + /// Return the record type pointed by the base pointer. + mlir::cir::PointerType getAddrTy() { return getAddr().getType(); } + + /// Return the result type. + mlir::cir::PointerType getResultTy() { + return getResult().getType().cast(); + } + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// BaseClassAddr +//===----------------------------------------------------------------------===// + +def BaseClassAddrOp : CIR_Op<"base_class_addr"> { + let summary = "Get the base class address for a class/struct"; + let description = [{ + The `cir.base_class_addr` operaration gets the address of a particular + base class given a derived class pointer. + + Example: + ```mlir + TBD + ``` + }]; + + let arguments = (ins + Arg:$derived_addr); + + let results = (outs Res:$base_addr); + + // FIXME: we should not be printing `cir.ptr` below, that should come + // from the pointer type directly. + let assemblyFormat = [{ + `(` + $derived_addr `:` `cir.ptr` type($derived_addr) + `)` `->` `cir.ptr` type($base_addr) attr-dict + }]; + + // FIXME: add verifier. + // Check whether both src/dst pointee's are compatible. + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// FuncOp +//===----------------------------------------------------------------------===// + +def FuncOp : CIR_Op<"func", [ + AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface, + IsolatedFromAbove, Symbol +]> { + let summary = "Declare or define a function"; + let description = [{ + + Similar to `mlir::FuncOp` built-in: + > Operations within the function cannot implicitly capture values defined + > outside of the function, i.e. Functions are `IsolatedFromAbove`. All + > external references must use function arguments or attributes that establish + > a symbolic connection (e.g. symbols referenced by name via a string + > attribute like SymbolRefAttr). An external function declaration (used when + > referring to a function declared in some other module) has no body. While + > the MLIR textual form provides a nice inline syntax for function arguments, + > they are internally represented as “block arguments” to the first block in + > the region. + > + > Only dialect attribute names may be specified in the attribute dictionaries + > for function arguments, results, or the function itself. + + The function linkage information is specified by `linkage`, as defined by + `GlobalLinkageKind` attribute. + + A compiler builtin function must be marked as `builtin` for further + processing when lowering from CIR. + + The `coroutine` keyword is used to mark coroutine function, which requires + at least one `cir.await` instruction to be used in its body. + + The `lambda` translates to a C++ `operator()` that implements a lambda, this + allow callsites to make certain assumptions about the real function nature + when writing analysis. The verifier should, but do act on this keyword yet. + + The `no_proto` keyword is used to identify functions that were declared + without a prototype and, consequently, may contain calls with invalid + arguments and undefined behavior. + + The `extra_attrs`, which is an aggregate of function-specific attributes is + required and mandatory to describle additional attributes that are not listed + above. Though mandatory, the prining of the attribute can be omitted if it is + empty. + + Example: + + ```mlir + // External function definitions. + cir.func @abort() + + // A function with internal linkage. + cir.func internal @count(%x: i64) -> (i64) + return %x : i64 + } + + // Linkage information + cir.func linkonce_odr @some_method(...) + + // Builtin function + cir.func builtin @__builtin_coro_end(!cir.ptr, !cir.bool) -> !cir.bool + + // Coroutine + cir.func coroutine @_Z10silly_taskv() -> !CoroTask { + ... + cir.await(...) + ... + } + ``` + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttrOf:$function_type, + UnitAttr:$builtin, + UnitAttr:$coroutine, + UnitAttr:$lambda, + UnitAttr:$no_proto, + DefaultValuedAttr:$linkage, + ExtraFuncAttr:$extra_attrs, + OptionalAttr:$sym_visibility, + OptionalAttr:$arg_attrs, + OptionalAttr:$res_attrs, + OptionalAttr:$aliasee, + OptionalAttr:$ast); + let regions = (region AnyRegion:$body); + let skipDefaultBuilders = 1; + + let builders = [OpBuilder<(ins + "StringRef":$name, "FuncType":$type, + CArg<"GlobalLinkageKind", "GlobalLinkageKind::ExternalLinkage">:$linkage, + CArg<"ArrayRef", "{}">:$attrs, + CArg<"ArrayRef", "{}">:$argAttrs) + >]; + + let extraClassDeclaration = [{ + /// Returns the region on the current operation that is callable. This may + /// return null in the case of an external callable object, e.g. an external + /// function. + ::mlir::Region *getCallableRegion(); + + /// Returns the results types that the callable region produces when + /// executed. + ArrayRef getCallableResults() { + if (::llvm::isa(getFunctionType().getReturnType())) + return {}; + return getFunctionType().getReturnTypes(); + } + + /// Returns the argument attributes for all callable region arguments or + /// null if there are none. + ::mlir::ArrayAttr getCallableArgAttrs() { + return getArgAttrs().value_or(nullptr); + } + + /// Returns the result attributes for all callable region results or null if + /// there are none. + ::mlir::ArrayAttr getCallableResAttrs() { + return getResAttrs().value_or(nullptr); + } + + /// Returns the argument types of this function. + ArrayRef getArgumentTypes() { return getFunctionType().getInputs(); } + + /// Returns the result types of this function. + ArrayRef getResultTypes() { return getFunctionType().getReturnTypes(); } + + /// Hook for OpTrait::FunctionOpInterfaceTrait, called after verifying that + /// the 'type' attribute is present and checks if it holds a function type. + /// Ensures getType, getNumFuncArguments, and getNumFuncResults can be + /// called safely. + LogicalResult verifyType(); + + //===------------------------------------------------------------------===// + // SymbolOpInterface Methods + //===------------------------------------------------------------------===// + + bool isDeclaration(); + }]; + + let hasCustomAssemblyFormat = 1; + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// CallOp +//===----------------------------------------------------------------------===// + +def CallOp : CIR_Op<"call", + [DeclareOpInterfaceMethods, DeclareOpInterfaceMethods]> { + let summary = "call operation"; + let description = [{ + The `call` operation represents a direct call to a function that is within + the same symbol scope as the call. The operands and result types of the + call must match the specified function type. The callee is encoded as a + symbol reference attribute named "callee". + + To walk the operands for this operation, use `getNumArgOperands()`, + `getArgOperand()`, `getArgOperands()`, `arg_operand_begin()` and + `arg_operand_begin()`. Avoid using `getNumOperands()`, `getOperand()`, + `operand_begin()`, etc, direclty - might be misleading given on indirect + calls the callee is encoded in the first operation operand. + `` + + Example: + + ```mlir + // Direct call + %2 = cir.call @my_add(%0, %1) : (f32, f32) -> f32 + ... + // Indirect call + %20 = cir.call %18(%17) + ``` + }]; + + let arguments = (ins OptionalAttr:$callee, Variadic:$operands); + let results = (outs Variadic); + + let builders = [ + OpBuilder<(ins "FuncOp":$callee, CArg<"ValueRange", "{}">:$operands), [{ + $_state.addOperands(operands); + $_state.addAttribute("callee", SymbolRefAttr::get(callee)); + if (!callee.getFunctionType().isVoid()) + $_state.addTypes(callee.getFunctionType().getReturnType()); + }]>, + OpBuilder<(ins "Value":$ind_target, + "FuncType":$fn_type, + CArg<"ValueRange", "{}">:$operands), [{ + $_state.addOperands(ValueRange{ind_target}); + $_state.addOperands(operands); + if (!fn_type.isVoid()) + $_state.addTypes(fn_type.getReturnType()); + }]>]; + + let extraClassDeclaration = [{ + mlir::Value getIndirectCallee() { + assert(!getCallee() && "only works for indirect call"); + return *arg_operand_begin(); + } + + operand_iterator arg_operand_begin() { + auto arg_begin = operand_begin(); + if (!getCallee()) + arg_begin++; + return arg_begin; + } + operand_iterator arg_operand_end() { return operand_end(); } + + /// Return the operand at index 'i', accounts for indirect call. + Value getArgOperand(unsigned i) { + if (!getCallee()) + i++; + return getOperand(i); + } + + /// Return the number of operands, , accounts for indirect call. + unsigned getNumArgOperands() { + if (!getCallee()) + return this->getOperation()->getNumOperands()-1; + return this->getOperation()->getNumOperands(); + } + }]; + + let hasCustomAssemblyFormat = 1; + let skipDefaultBuilders = 1; + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// AwaitOp +//===----------------------------------------------------------------------===// + +def AK_Initial : I32EnumAttrCase<"init", 1>; +def AK_User : I32EnumAttrCase<"user", 2>; +def AK_Final : I32EnumAttrCase<"final", 3>; + +def AwaitKind : I32EnumAttr< + "AwaitKind", + "await kind", + [AK_Initial, AK_User, AK_Final]> { + let cppNamespace = "::mlir::cir"; +} + +def AwaitOp : CIR_Op<"await", + [DeclareOpInterfaceMethods, + RecursivelySpeculatable, NoRegionArguments]> { + let summary = "Wraps C++ co_await implicit logic"; + let description = [{ + The under the hood effect of using C++ `co_await expr` roughly + translates to: + + ```c++ + // co_await expr; + + auto &&x = CommonExpr(); + if (!x.await_ready()) { + ... + x.await_suspend(...); + ... + } + x.await_resume(); + ``` + + `cir.await` represents this logic by using 3 regions: + - ready: covers veto power from x.await_ready() + - suspend: wraps actual x.await_suspend() logic + - resume: handles x.await_resume() + + Breaking this up in regions allow individual scrutiny of conditions + which might lead to folding some of them out. Lowerings coming out + of CIR, e.g. LLVM, should use the `suspend` region to track more + lower level codegen (e.g. intrinsic emission for coro.save/coro.suspend). + + There are also 3 flavors of `cir.await` available: + - `init`: compiler generated initial suspend via implicit `co_await`. + - `user`: also known as normal, representing user written co_await's. + - `final`: compiler generated final suspend via implicit `co_await`. + + From the C++ snippet we get: + + ```mlir + cir.scope { + ... // auto &&x = CommonExpr(); + cir.await(user, ready : { + ... // x.await_ready() + }, suspend : { + ... // x.await_suspend() + }, resume : { + ... // x.await_resume() + }) + } + ``` + + Note that resulution of the common expression is assumed to happen + as part of the enclosing await scope. + }]; + + let arguments = (ins AwaitKind:$kind); + let regions = (region SizedRegion<1>:$ready, + SizedRegion<1>:$suspend, + SizedRegion<1>:$resume); + let assemblyFormat = [{ + `(` $kind `,` + `ready` `:` $ready `,` + `suspend` `:` $suspend `,` + `resume` `:` $resume `,` + `)` + attr-dict + }]; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins + "mlir::cir::AwaitKind":$kind, + CArg<"function_ref", + "nullptr">:$readyBuilder, + CArg<"function_ref", + "nullptr">:$suspendBuilder, + CArg<"function_ref", + "nullptr">:$resumeBuilder + )> + ]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +def TryOp : CIR_Op<"try", + [DeclareOpInterfaceMethods, + RecursivelySpeculatable, NoRegionArguments]> { + let summary = ""; + let description = [{ + ```mlir + cir.scope { + // Selector and exception control related allocas + // C++ `try {}` local variable declarations + %except_info = cir.try { + %res0, %exh = cir.call @return_something() + %if %exh + cir.yield %exh + + %exh2 = cir.call @return_void() + %if %exh2 + cir.yield %exh + cir.yield #zero : !except_type + } + ... + cir.br ^cleanup + ^cleanup: + // Run dtors + ... + // Catch based %except_info + cir.catch(%except_info, [ + /*catch A*/ {}, + /*catch B*/ {}, + ... + all {} + ]) + cir.yield + } + ``` + + Note that variables declared inside a `try {}` in C++ will + have their allocas places in the surrounding scope. + }]; + + let regions = (region SizedRegion<1>:$body); + // FIXME: should be exception type. + let results = (outs AnyType:$result); + + let assemblyFormat = [{ + `{` + $body + `}` `:` functional-type(operands, results) attr-dict + }]; + + // Everything already covered elswhere. + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// CatchOp +//===----------------------------------------------------------------------===// + +def CatchEntryAttr : AttrDef { + let parameters = (ins "mlir::Type":$exception_type, + "Attribute":$exception_type_info); + let mnemonic = "type"; + let assemblyFormat = "`<` struct(params) `>`"; +} + +def CatchArrayAttr : + TypedArrayAttrBase { + let constBuilderCall = ?; +} + +def CatchOp : CIR_Op<"catch", + [SameVariadicOperandSize, + DeclareOpInterfaceMethods, + RecursivelySpeculatable, NoRegionArguments]> { + let summary = "Catch operation"; + let description = [{ + }]; + + let arguments = (ins AnyType:$exception_info, + OptionalAttr:$catchers); + let regions = (region VariadicRegion:$regions); + + // Already verified elsewhere + let hasVerifier = 0; + + let skipDefaultBuilders = 1; + let builders = [ + OpBuilder<(ins + "function_ref" + :$catchBuilder)> + ]; + + let assemblyFormat = [{ + `(` + $exception_info `:` type($exception_info) `,` + custom($regions, $catchers) + `)` attr-dict + }]; +} + +//===----------------------------------------------------------------------===// +// CopyOp +//===----------------------------------------------------------------------===// + +def CopyOp : CIR_Op<"copy", [SameTypeOperands]> { + let arguments = (ins Arg:$dst, + Arg:$src); + let summary = "Copies contents from a CIR pointer to another"; + let description = [{ + Given two CIR pointers, `src` and `dst`, `cir.copy` will copy the memory + pointed by `src` to the memory pointed by `dst`. + + The amount of bytes copied is inferred from the pointee type. Naturally, + the pointee type of both `src` and `dst` must match and must implement + the `DataLayoutTypeInterface`. + + Examples: + + ```mlir + // Copying contents from one struct to another: + cir.copy %0 to %1 : !cir.ptr + ``` + }]; + + let assemblyFormat = "$src `to` $dst attr-dict `:` qualified(type($dst))"; + let hasVerifier = 1; + + let extraClassDeclaration = [{ + /// Returns the pointer type being copied. + mlir::cir::PointerType getType() { return getSrc().getType(); } + + /// Returns the number of bytes to be copied. + unsigned getLength() { + return DataLayout::closest(*this).getTypeSize(getType().getPointee()); + } + }]; +} + +//===----------------------------------------------------------------------===// +// MemCpyOp +//===----------------------------------------------------------------------===// + +def MemCpyOp : CIR_Op<"libc.memcpy"> { + let arguments = (ins Arg:$dst, + Arg:$src, + CIR_IntType:$len); + let summary = "Equivalent to libc's `memcpy`"; + let description = [{ + Given two CIR pointers, `src` and `dst`, `cir.libc.memcpy` will copy `len` + bytes from the memory pointed by `src` to the memory pointed by `dst`. + + While `cir.copy` is meant to be used for implicit copies in the code where + the length of the copy is known, `cir.memcpy` copies only from and to void + pointers, requiring the copy length to be passed as an argument. + + Examples: + + ```mlir + // Copying 2 bytes from one array to a struct: + %2 = cir.const(#cir.int<2> : !u32i) : !u32i + cir.libc.memcpy %2 bytes from %arr to %struct : !cir.ptr -> !cir.ptr + ``` + }]; + + let assemblyFormat = [{ + $len `bytes` `from` $src `to` $dst attr-dict + `:` type($len) `` `,` qualified(type($src)) `->` qualified(type($dst)) + }]; + let hasVerifier = 1; + + let extraClassDeclaration = [{ + /// Returns the data source pointer type. + mlir::cir::PointerType getSrcTy() { return getSrc().getType(); } + + /// Returns the data destination pointer type. + mlir::cir::PointerType getDstTy() { return getDst().getType(); } + + /// Returns the byte length type. + mlir::cir::IntType getLenTy() { return getLen().getType(); } + }]; +} + +//===----------------------------------------------------------------------===// +// FAbsOp +//===----------------------------------------------------------------------===// + +def FAbsOp : CIR_Op<"fabs", [Pure, SameOperandsAndResultType]> { + let arguments = (ins AnyFloat:$src); + let results = (outs AnyFloat:$result); + let summary = "Returns absolute value for floating-point input."; + let description = [{ + Equivalent to libc's `fabs` and LLVM's intrinsic with the same name. + + Examples: + + ```mlir + %1 = cir.const(1.0 : f64) : f64 + %2 = cir.fabs %1 : f64 + ``` + }]; + + let assemblyFormat = [{ + $src `:` type($src) attr-dict + }]; + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// Variadic Operations +//===----------------------------------------------------------------------===// + +def VAStartOp : CIR_Op<"va.start">, Arguments<(ins CIR_PointerType:$arg_list)> { + let summary = "Starts a variable argument list"; + let assemblyFormat = "$arg_list attr-dict `:` type(operands)"; + let hasVerifier = 0; +} + +def VAEndOp : CIR_Op<"va.end">, Arguments<(ins CIR_PointerType:$arg_list)> { + let summary = "Ends a variable argument list"; + let assemblyFormat = "$arg_list attr-dict `:` type(operands)"; + let hasVerifier = 0; +} + +def VACopyOp : CIR_Op<"va.copy">, + Arguments<(ins CIR_PointerType:$dst_list, + CIR_PointerType:$src_list)> { + let summary = "Copies a variable argument list"; + let assemblyFormat = "$src_list `to` $dst_list attr-dict `:` type(operands)"; + let hasVerifier = 0; +} + +def VAArgOp : CIR_Op<"va.arg">, + Results<(outs AnyType:$result)>, + Arguments<(ins CIR_PointerType:$arg_list)> { + let summary = "Fetches next variadic element as a given type"; + let assemblyFormat = "$arg_list attr-dict `:` functional-type(operands, $result)"; + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// AllocException +//===----------------------------------------------------------------------===// + +def AllocException : CIR_Op<"alloc_exception", [ + AllocaTypesMatchWith<"'allocType' matches pointee type of 'addr'", + "addr", "allocType", + "$_self.cast().getPointee()">]> { + let summary = "Defines a scope-local variable"; + let description = [{ + Implements a slightly higher level __cxa_allocate_exception: + + `void *__cxa_allocate_exception(size_t thrown_size);` + + If operation fails, program terminates, not throw. + + Example: + + ```mlir + // if (b == 0) { + // ... + // throw "..."; + cir.if %10 { + %11 = cir.alloc_exception(!cir.ptr) -> > + ... // store exception content into %11 + cir.throw(%11 : !cir.ptr>, ... + ``` + }]; + + let arguments = (ins TypeAttr:$allocType); + let results = (outs Res]>:$addr); + + let assemblyFormat = [{ + `(` $allocType `)` `->` type($addr) attr-dict + }]; + + // Constraints verified elsewhere. + let hasVerifier = 0; +} + +//===----------------------------------------------------------------------===// +// ThrowOp +//===----------------------------------------------------------------------===// + +def ThrowOp : CIR_Op<"throw", + [HasParent<"FuncOp, ScopeOp, IfOp, SwitchOp, LoopOp">, + Terminator]> { + let summary = "(Re)Throws an exception"; + let description = [{ + Very similar to __cxa_throw: + + ``` + void __cxa_throw(void *thrown_exception, std::type_info *tinfo, + void (*dest) (void *)); + ``` + + The absense of arguments for `cir.throw` means it rethrows. + + For the no-rethrow version, it must have at least two operands, the RTTI + information, a pointer to the exception object (likely allocated via + `cir.cxa.allocate_exception`) and finally an optional dtor, which might + run as part of this operation. + + ```mlir + // if (b == 0) + // throw "Division by zero condition!"; + cir.if %10 { + %11 = cir.alloc_exception(!cir.ptr) -> > + ... + cir.store %13, %11 : // Store string addr for "Division by zero condition!" + cir.throw(%11 : !cir.ptr>, @"typeinfo for char const*") + ``` + }]; + + let arguments = (ins Optional:$exception_ptr, + OptionalAttr:$type_info, + OptionalAttr:$dtor); + + let assemblyFormat = [{ + `(` + ($exception_ptr^ `:` type($exception_ptr))? + (`,` $type_info^)? + (`,` $dtor^)? + `)` attr-dict + }]; + + let extraClassDeclaration = [{ + bool rethrows() { return getNumOperands() == 0; } + }]; + + let hasVerifier = 1; +} + +//===----------------------------------------------------------------------===// +// Operations Lowered Directly to LLVM IR +// +// These operations are hacks to get around missing features in LLVM's dialect. +// Use it sparingly and remove it once the features are added. +//===----------------------------------------------------------------------===// + +def ZeroInitConstOp : CIR_Op<"llvmir.zeroinit", [Pure]>, + Results<(outs AnyType:$result)> { + let summary = "Zero initializes a constant value of a given type"; + let description = [{ + This operation circumvents the lack of a zeroinitializer operation in LLVM + Dialect. It can zeroinitialize any LLVM type. + }]; + let assemblyFormat = "attr-dict `:` type($result)"; + let hasVerifier = 0; +} + +#endif // MLIR_CIR_DIALECT_CIR_OPS diff --git a/clang/include/clang/CIR/Dialect/IR/CIROpsEnums.h b/clang/include/clang/CIR/Dialect/IR/CIROpsEnums.h new file mode 100644 index 000000000000..889cde696e91 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIROpsEnums.h @@ -0,0 +1,121 @@ +//===- CIROpsEnumsDialect.h - MLIR Dialect for CIR ----------------------*- C++ +//-*-===// +// +// 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 declares the Target dialect for CIR in MLIR. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_CIR_CIROPSENUMS_H_ +#define MLIR_DIALECT_CIR_CIROPSENUMS_H_ + +#include "mlir/IR/BuiltinAttributes.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h.inc" + +namespace mlir { +namespace cir { + +static bool isExternalLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::ExternalLinkage; +} +static bool isAvailableExternallyLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::AvailableExternallyLinkage; +} +static bool isLinkOnceAnyLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::LinkOnceAnyLinkage; +} +static bool isLinkOnceODRLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::LinkOnceODRLinkage; +} +static bool isLinkOnceLinkage(GlobalLinkageKind Linkage) { + return isLinkOnceAnyLinkage(Linkage) || isLinkOnceODRLinkage(Linkage); +} +static bool isWeakAnyLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::WeakAnyLinkage; +} +static bool isWeakODRLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::WeakODRLinkage; +} +static bool isWeakLinkage(GlobalLinkageKind Linkage) { + return isWeakAnyLinkage(Linkage) || isWeakODRLinkage(Linkage); +} +static bool isInternalLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::InternalLinkage; +} +static bool isPrivateLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::PrivateLinkage; +} +static bool isLocalLinkage(GlobalLinkageKind Linkage) { + return isInternalLinkage(Linkage) || isPrivateLinkage(Linkage); +} +static bool isExternalWeakLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::ExternalWeakLinkage; +} +LLVM_ATTRIBUTE_UNUSED static bool isCommonLinkage(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::CommonLinkage; +} +LLVM_ATTRIBUTE_UNUSED static bool +isValidDeclarationLinkage(GlobalLinkageKind Linkage) { + return isExternalWeakLinkage(Linkage) || isExternalLinkage(Linkage); +} + +/// Whether the definition of this global may be replaced by something +/// non-equivalent at link time. For example, if a function has weak linkage +/// then the code defining it may be replaced by different code. +LLVM_ATTRIBUTE_UNUSED static bool +isInterposableLinkage(GlobalLinkageKind Linkage) { + switch (Linkage) { + case GlobalLinkageKind::WeakAnyLinkage: + case GlobalLinkageKind::LinkOnceAnyLinkage: + case GlobalLinkageKind::CommonLinkage: + case GlobalLinkageKind::ExternalWeakLinkage: + return true; + + case GlobalLinkageKind::AvailableExternallyLinkage: + case GlobalLinkageKind::LinkOnceODRLinkage: + case GlobalLinkageKind::WeakODRLinkage: + // The above three cannot be overridden but can be de-refined. + + case GlobalLinkageKind::ExternalLinkage: + case GlobalLinkageKind::InternalLinkage: + case GlobalLinkageKind::PrivateLinkage: + return false; + } + llvm_unreachable("Fully covered switch above!"); +} + +/// Whether the definition of this global may be discarded if it is not used +/// in its compilation unit. +LLVM_ATTRIBUTE_UNUSED static bool +isDiscardableIfUnused(GlobalLinkageKind Linkage) { + return isLinkOnceLinkage(Linkage) || isLocalLinkage(Linkage) || + isAvailableExternallyLinkage(Linkage); +} + +/// Whether the definition of this global may be replaced at link time. NB: +/// Using this method outside of the code generators is almost always a +/// mistake: when working at the IR level use isInterposable instead as it +/// knows about ODR semantics. +LLVM_ATTRIBUTE_UNUSED static bool isWeakForLinker(GlobalLinkageKind Linkage) { + return Linkage == GlobalLinkageKind::WeakAnyLinkage || + Linkage == GlobalLinkageKind::WeakODRLinkage || + Linkage == GlobalLinkageKind::LinkOnceAnyLinkage || + Linkage == GlobalLinkageKind::LinkOnceODRLinkage || + Linkage == GlobalLinkageKind::CommonLinkage || + Linkage == GlobalLinkageKind::ExternalWeakLinkage; +} + +LLVM_ATTRIBUTE_UNUSED static bool isValidLinkage(GlobalLinkageKind L) { + return isExternalLinkage(L) || isLocalLinkage(L) || isWeakLinkage(L) || + isLinkOnceLinkage(L); +} + +} // namespace cir +} // namespace mlir + +#endif // MLIR_DIALECT_CIR_CIROPSENUMS_H_ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.h b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h new file mode 100644 index 000000000000..1286225f04aa --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.h @@ -0,0 +1,29 @@ +//===- CIRTypes.h - MLIR CIR Types ------------------------------*- C++ -*-===// +// +// 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 declares the types in the CIR dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_CIR_IR_CIRTYPES_H_ +#define MLIR_DIALECT_CIR_IR_CIRTYPES_H_ + +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Types.h" +#include "mlir/Interfaces/DataLayoutInterfaces.h" + +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" + +//===----------------------------------------------------------------------===// +// CIR Dialect Types +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "clang/CIR/Dialect/IR/CIROpsTypes.h.inc" + +#endif // MLIR_DIALECT_CIR_IR_CIRTYPES_H_ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypes.td b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td new file mode 100644 index 000000000000..96078c8bbeb5 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRTypes.td @@ -0,0 +1,270 @@ +//===- CIRTypes.td - CIR dialect types ---------------------*- tablegen -*-===// +// +// 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 declares the CIR dialect types. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_CIR_DIALECT_CIR_TYPES +#define MLIR_CIR_DIALECT_CIR_TYPES + +include "clang/CIR/Dialect/IR/CIRDialect.td" +include "clang/CIR/Interfaces/ASTAttrInterfaces.td" +include "mlir/Interfaces/DataLayoutInterfaces.td" +include "mlir/IR/AttrTypeBase.td" + +//===----------------------------------------------------------------------===// +// CIR Types +//===----------------------------------------------------------------------===// + +class CIR_Type traits = []> : + TypeDef { + let mnemonic = typeMnemonic; +} + +//===----------------------------------------------------------------------===// +// IntType +//===----------------------------------------------------------------------===// + +def CIR_IntType : CIR_Type<"Int", "int", + [DeclareTypeInterfaceMethods]> { + let summary = "Integer type with arbitrary precision up to a fixed limit"; + let description = [{ + CIR type that represents C/C++ primitive integer types. + Said types are: `char`, `short`, `int`, `long`, `long long`, and their \ + unsigned variations. + }]; + let parameters = (ins "unsigned":$width, "bool":$isSigned); + let hasCustomAssemblyFormat = 1; + let extraClassDeclaration = [{ + /// Return true if this is a signed integer type. + bool isSigned() const { return getIsSigned(); } + /// Return true if this is an unsigned integer type. + bool isUnsigned() const { return !getIsSigned(); } + /// Return type alias. + std::string getAlias() const { + return (isSigned() ? 's' : 'u') + std::to_string(getWidth()) + 'i'; + }; + }]; + let genVerifyDecl = 1; +} + +//===----------------------------------------------------------------------===// +// PointerType +//===----------------------------------------------------------------------===// + +def CIR_PointerType : CIR_Type<"Pointer", "ptr", + [DeclareTypeInterfaceMethods]> { + + let summary = "CIR pointer type"; + let description = [{ + `CIR.ptr` is a type returned by any op generating a pointer in C++. + }]; + + let parameters = (ins "mlir::Type":$pointee); + + let hasCustomAssemblyFormat = 1; +} + +//===----------------------------------------------------------------------===// +// BoolType +// +// An alternative here is to represent bool as mlir::i1, but let's be more +// generic. +// +//===----------------------------------------------------------------------===// +def CIR_BoolType : + CIR_Type<"Bool", "bool", + [DeclareTypeInterfaceMethods]> { + + let summary = "CIR bool type"; + let description = [{ + `cir.bool` represent's C++ bool type. + }]; + + let hasCustomAssemblyFormat = 1; +} + +//===----------------------------------------------------------------------===// +// StructType +// +// The base type for all RecordDecls. +// +//===----------------------------------------------------------------------===// + +def CIR_StructType : CIR_Type<"Struct", "struct", + [DeclareTypeInterfaceMethods]> { + + let summary = "CIR struct type"; + let description = [{ + Each unique clang::RecordDecl is mapped to a `cir.struct` and any object in + C/C++ that has a struct type will have a `cir.struct` in CIR. + }]; + + let parameters = (ins + ArrayRefParameter<"mlir::Type", "members">:$members, + "mlir::StringAttr":$name, + "bool":$incomplete, + "bool":$packed, + "mlir::cir::StructType::RecordKind":$kind, + "ASTRecordDeclInterface":$ast + ); + + let hasCustomAssemblyFormat = 1; + + let extraClassDeclaration = [{ + enum RecordKind : uint32_t { + Class, + Union, + Struct + }; + + private: + // All these support lazily computation and storage + // for the struct size and alignment. + mutable std::optional size{}, align{}; + mutable std::optional padded{}; + mutable mlir::Type largestMember{}; + void computeSizeAndAlignment(const ::mlir::DataLayout &dataLayout) const; + public: + void dropAst(); + size_t getNumElements() const { return getMembers().size(); } + bool isIncomplete() const { return getIncomplete(); } + bool isComplete() const { return !getIncomplete(); } + bool isPadded(const ::mlir::DataLayout &dataLayout) const; + + std::string getPrefixedName() { + const auto name = getName().getValue().str(); + switch (getKind()) { + case RecordKind::Class: + return "class." + name; + case RecordKind::Union: + return "union." + name; + case RecordKind::Struct: + return "struct." + name; + } + } + + /// Return the member with the largest bit-length. + mlir::Type getLargestMember(const ::mlir::DataLayout &dataLayout) const; + + /// Return whether this is a class declaration. + bool isClass() const { return getKind() == RecordKind::Class; } + + /// Return whether this is a union declaration. + bool isUnion() const { return getKind() == RecordKind::Union; } + + /// Return whether this is a struct declaration. + bool isStruct() const { return getKind() == RecordKind::Struct; } + }]; + + let extraClassDefinition = [{ + void $cppClass::dropAst() { + getImpl()->ast = nullptr; + } + }]; +} + +//===----------------------------------------------------------------------===// +// ArrayType +//===----------------------------------------------------------------------===// + +def CIR_ArrayType : CIR_Type<"Array", "array", + [DeclareTypeInterfaceMethods]> { + + let summary = "CIR array type"; + let description = [{ + `CIR.array` represents C/C++ constant arrays. + }]; + + let parameters = (ins "mlir::Type":$eltType, "uint64_t":$size); + + let assemblyFormat = [{ + `<` $eltType `x` $size `>` + }]; +} + +//===----------------------------------------------------------------------===// +// FuncType +//===----------------------------------------------------------------------===// + +def CIR_FuncType : CIR_Type<"Func", "func"> { + let summary = "CIR function type"; + let description = [{ + The `!cir.func` is a function type. It consists of a single return type, a + list of parameter types and can optionally be variadic. + + Example: + + ```mlir + !cir.func + !cir.func + !cir.func + ``` + }]; + + let parameters = (ins ArrayRefParameter<"Type">:$inputs, "Type":$returnType, + "bool":$varArg); + let assemblyFormat = [{ + `<` $returnType ` ` `(` custom($inputs, $varArg) `>` + }]; + + let builders = [ + TypeBuilderWithInferredContext<(ins + "ArrayRef":$inputs, "Type":$returnType, + CArg<"bool", "false">:$isVarArg), [{ + return $_get(returnType.getContext(), inputs, returnType, isVarArg); + }]> + ]; + + let extraClassDeclaration = [{ + /// Returns whether the function is variadic. + bool isVarArg() const { return getVarArg(); } + + /// Returns the `i`th input operand type. Asserts if out of bounds. + Type getInput(unsigned i) const { return getInputs()[i]; } + + /// Returns the number of arguments to the function. + unsigned getNumInputs() const { return getInputs().size(); } + + /// Returns the result type of the function as an ArrayRef, enabling better + /// integration with generic MLIR utilities. + ArrayRef getReturnTypes() const; + + /// Returns whether the function is returns void. + bool isVoid() const; + + /// Returns a clone of this function type with the given argument + /// and result types. + FuncType clone(TypeRange inputs, TypeRange results) const; + }]; +} + +//===----------------------------------------------------------------------===// +// Void type +//===----------------------------------------------------------------------===// + +def CIR_VoidType : CIR_Type<"Void", "void"> { + let summary = "CIR void type"; + let description = [{ + The `!cir.void` type represents the C/C++ `void` type. + }]; + let extraClassDeclaration = [{ + /// Returns a clone of this type with the given context. + std::string getAlias() const { return "void"; }; + }]; +} + +//===----------------------------------------------------------------------===// +// One type to bind them all +//===----------------------------------------------------------------------===// + +def CIR_AnyCIRType : AnyTypeOf<[CIR_PointerType, CIR_BoolType, CIR_StructType, + CIR_ArrayType, CIR_FuncType, CIR_VoidType]>; + +#endif // MLIR_CIR_DIALECT_CIR_TYPES diff --git a/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt b/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt new file mode 100644 index 000000000000..b3d86c631b86 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CMakeLists.txt @@ -0,0 +1,29 @@ +# This replicates part of the add_mlir_dialect cmake function from MLIR that +# cannot be used here. This happens because it expects to be run inside MLIR +# directory which is not the case for CIR (and also FIR, both have similar +# workarounds). + +# Equivalent to add_mlir_dialect(CIROps cir) +set(LLVM_TARGET_DEFINITIONS CIROps.td) +mlir_tablegen(CIROps.h.inc -gen-op-decls) +mlir_tablegen(CIROps.cpp.inc -gen-op-defs) +mlir_tablegen(CIROpsTypes.h.inc -gen-typedef-decls) +mlir_tablegen(CIROpsTypes.cpp.inc -gen-typedef-defs) +mlir_tablegen(CIROpsDialect.h.inc -gen-dialect-decls) +mlir_tablegen(CIROpsDialect.cpp.inc -gen-dialect-defs) +add_public_tablegen_target(MLIRCIROpsIncGen) +add_dependencies(mlir-headers MLIRCIROpsIncGen) + +# Equivalent to add_mlir_doc +add_clang_mlir_doc(CIROps CIROps Dialects/ -gen-op-doc) +add_clang_mlir_doc(CIRAttrs CIRAttrs Dialects/ -gen-attrdef-doc) +add_clang_mlir_doc(CIRTypes CIRTypes Dialects/ -gen-typedef-doc) + +# Generate extra headers for custom enum and attrs. +mlir_tablegen(CIROpsEnums.h.inc -gen-enum-decls) +mlir_tablegen(CIROpsEnums.cpp.inc -gen-enum-defs) +mlir_tablegen(CIROpsStructs.h.inc -gen-attrdef-defs) +mlir_tablegen(CIROpsStructs.cpp.inc -gen-attrdef-defs) +mlir_tablegen(CIROpsAttributes.h.inc -gen-attrdef-decls) +mlir_tablegen(CIROpsAttributes.cpp.inc -gen-attrdef-defs) +add_public_tablegen_target(MLIRCIREnumsGen) diff --git a/clang/include/clang/CIR/Dialect/IR/FPEnv.h b/clang/include/clang/CIR/Dialect/IR/FPEnv.h new file mode 100644 index 000000000000..aceba9ee57d0 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/FPEnv.h @@ -0,0 +1,50 @@ +//===- FPEnv.h ---- FP Environment ------------------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +/// @file +/// This file contains the declarations of entities that describe floating +/// point environment and related functions. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_CIR_DIALECT_IR_FPENV_H +#define CLANG_CIR_DIALECT_IR_FPENV_H + +#include "llvm/ADT/FloatingPointMode.h" + +#include + +namespace cir { + +namespace fp { + +/// Exception behavior used for floating point operations. +/// +/// Each of these values corresponds to some LLVMIR metadata argument value of a +/// constrained floating point intrinsic. See the LLVM Language Reference Manual +/// for details. +enum ExceptionBehavior : uint8_t { + ebIgnore, ///< This corresponds to "fpexcept.ignore". + ebMayTrap, ///< This corresponds to "fpexcept.maytrap". + ebStrict, ///< This corresponds to "fpexcept.strict". +}; + +} // namespace fp + +/// For any RoundingMode enumerator, returns a string valid as input in +/// constrained intrinsic rounding mode metadata. +std::optional convertRoundingModeToStr(llvm::RoundingMode); + +/// For any ExceptionBehavior enumerator, returns a string valid as input in +/// constrained intrinsic exception behavior metadata. +std::optional + convertExceptionBehaviorToStr(fp::ExceptionBehavior); + +} // namespace cir + +#endif diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h new file mode 100644 index 000000000000..abf915bf687a --- /dev/null +++ b/clang/include/clang/CIR/Dialect/Passes.h @@ -0,0 +1,44 @@ +//===- Passes.h - CIR pass entry points -------------------------*- C++ -*-===// +// +// 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 header file defines prototypes that expose pass constructors. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_CIR_PASSES_H_ +#define MLIR_DIALECT_CIR_PASSES_H_ + +#include "mlir/Pass/Pass.h" + +namespace clang { +class ASTContext; +} +namespace mlir { + +std::unique_ptr createLifetimeCheckPass(); +std::unique_ptr createLifetimeCheckPass(clang::ASTContext *astCtx); +std::unique_ptr createLifetimeCheckPass(ArrayRef remark, + ArrayRef hist, + unsigned hist_limit, + clang::ASTContext *astCtx); +std::unique_ptr createMergeCleanupsPass(); +std::unique_ptr createDropASTPass(); +std::unique_ptr createLoweringPreparePass(); +std::unique_ptr createLoweringPreparePass(clang::ASTContext *astCtx); + +//===----------------------------------------------------------------------===// +// Registration +//===----------------------------------------------------------------------===// + +/// Generate the code for registering passes. +#define GEN_PASS_REGISTRATION +#include "clang/CIR/Dialect/Passes.h.inc" + +} // namespace mlir + +#endif // MLIR_DIALECT_CIR_PASSES_H_ diff --git a/clang/include/clang/CIR/Dialect/Passes.td b/clang/include/clang/CIR/Dialect/Passes.td new file mode 100644 index 000000000000..08c95ab92ed7 --- /dev/null +++ b/clang/include/clang/CIR/Dialect/Passes.td @@ -0,0 +1,78 @@ +//===-- Passes.td - CIR pass definition file ---------------*- tablegen -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_DIALECT_CIR_PASSES +#define MLIR_DIALECT_CIR_PASSES + +include "mlir/Pass/PassBase.td" + +def MergeCleanups : Pass<"cir-merge-cleanups"> { + let summary = "Remove unnecessary branches to cleanup blocks"; + let description = [{ + Canonicalize pass is too aggressive for CIR when the pipeline is + used for C/C++ analysis. This pass runs some rewrites for scopes, + merging some blocks and eliminating unnecessary control-flow. + }]; + let constructor = "mlir::createMergeCleanupsPass()"; + let dependentDialects = ["cir::CIRDialect"]; +} + +def LifetimeCheck : Pass<"cir-lifetime-check"> { + let summary = "Check lifetime safety and generate diagnostics"; + let description = [{ + This pass relies on a lifetime analysis pass and uses the diagnostics + mechanism to report to the user. It does not change any code. + + A default ctor is specified but is solely in order to make + tablegen happy, since this pass requires the presence of an ASTContext, + one can set that up using `mlir::createLifetimeCheckPass(clang::ASTContext &)` + instead. + }]; + let constructor = "mlir::createLifetimeCheckPass()"; + let dependentDialects = ["cir::CIRDialect"]; + + let options = [ + ListOption<"historyList", "history", "std::string", + "List of history styles to emit as part of diagnostics." + " Supported styles: {all|null|invalid}", "llvm::cl::ZeroOrMore">, + ListOption<"remarksList", "remarks", "std::string", + "List of remark styles to enable as part of diagnostics." + " Supported styles: {all|pset}", "llvm::cl::ZeroOrMore">, + Option<"historyLimit", "history_limit", "unsigned", /*default=*/"1", + "Max amount of diagnostics to emit on pointer history"> + ]; +} + +def DropAST : Pass<"cir-drop-ast"> { + let summary = "Remove clang AST nodes attached to CIR operations"; + let description = [{ + Some CIR operations have references back to Clang AST, this is + necessary to perform lots of useful checks without having to + duplicate all rich AST information in CIR. As we move down in the + pipeline (e.g. generating LLVM or other MLIR dialects), the need + for such nodes diminish and AST information can be dropped. + + Right now this is enabled by default in Clang prior to dialect + codegen from CIR, but not before lifetime check, where AST is + required to be present. + }]; + let constructor = "mlir::createDropASTPass()"; + let dependentDialects = ["cir::CIRDialect"]; +} + +def LoweringPrepare : Pass<"cir-lowering-prepare"> { + let summary = "Preparation work before lowering to LLVM dialect"; + let description = [{ + This pass does preparation work for LLVM lowering. For example, it may + expand the global variable initialziation in a more ABI-friendly form. + }]; + let constructor = "mlir::createLoweringPreparePass()"; + let dependentDialects = ["cir::CIRDialect"]; +} + +#endif // MLIR_DIALECT_CIR_PASSES diff --git a/clang/include/clang/CIR/Dialect/Transforms/CMakeLists.txt b/clang/include/clang/CIR/Dialect/Transforms/CMakeLists.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h new file mode 100644 index 000000000000..e2f1e16eb511 --- /dev/null +++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.h @@ -0,0 +1,45 @@ +//===- ASTAttrInterfaces.h - CIR AST Interfaces -----------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_INTERFACES_CIR_AST_ATTR_INTERFACES_H_ +#define MLIR_INTERFACES_CIR_AST_ATTR_INTERFACES_H_ + +#include "mlir/IR/Attributes.h" + +#include "clang/AST/Attr.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/Mangle.h" + +namespace mlir { +namespace cir { + +mlir::Attribute makeFuncDeclAttr(const clang::Decl *decl, + mlir::MLIRContext *ctx); + +} // namespace cir +} // namespace mlir + +/// Include the generated interface declarations. +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h.inc" + +namespace mlir { +namespace cir { + +template bool hasAttr(ASTDeclInterface decl) { + if constexpr (std::is_same_v) + return decl.hasOwnerAttr(); + if constexpr (std::is_same_v) + return decl.hasPointerAttr(); + if constexpr (std::is_same_v) + return decl.hasInitPriorityAttr(); +} + +} // namespace cir +} // namespace mlir + +#endif // MLIR_INTERFACES_CIR_AST_ATAR_INTERFACES_H_ diff --git a/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td new file mode 100644 index 000000000000..8aca1d9c8e63 --- /dev/null +++ b/clang/include/clang/CIR/Interfaces/ASTAttrInterfaces.td @@ -0,0 +1,191 @@ +//===- ASTAttrInterfaces.td - CIR AST Interface Definitions -----*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_CIR_INTERFACES_AST_ATTR_INTERFACES +#define MLIR_CIR_INTERFACES_AST_ATTR_INTERFACES + +include "mlir/IR/OpBase.td" + +let cppNamespace = "::mlir::cir" in { + def ASTDeclInterface : AttrInterface<"ASTDeclInterface"> { + let methods = [ + InterfaceMethod<"", "bool", "hasOwnerAttr", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->template hasAttr(); + }] + >, + InterfaceMethod<"", "bool", "hasPointerAttr", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->template hasAttr(); + }] + >, + InterfaceMethod<"", "bool", "hasInitPriorityAttr", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->template hasAttr(); + }] + > + ]; + } + + def ASTNamedDeclInterface : AttrInterface<"ASTNamedDeclInterface", + [ASTDeclInterface]> { + let methods = [ + InterfaceMethod<"", "clang::DeclarationName", "getDeclName", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->getDeclName(); + }] + >, + InterfaceMethod<"", "llvm::StringRef", "getName", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->getName(); + }] + > + ]; + } + + def ASTValueDeclInterface : AttrInterface<"ASTValueDeclInterface", + [ASTNamedDeclInterface]>; + + def ASTDeclaratorDeclInterface : AttrInterface<"ASTDeclaratorDeclInterface", + [ASTValueDeclInterface]>; + + def ASTVarDeclInterface : AttrInterface<"ASTVarDeclInterface", + [ASTDeclaratorDeclInterface]> { + let methods = [ + InterfaceMethod<"", "void", "mangleDynamicInitializer", (ins "llvm::raw_ostream&":$Out), [{}], + /*defaultImplementation=*/ [{ + std::unique_ptr MangleCtx( + $_attr.getAstDecl()->getASTContext().createMangleContext()); + MangleCtx->mangleDynamicInitializer($_attr.getAstDecl(), Out); + }] + >, + InterfaceMethod<"", "clang::VarDecl::TLSKind", "getTLSKind", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->getTLSKind(); + }] + > + ]; + } + + def ASTFunctionDeclInterface : AttrInterface<"ASTFunctionDeclInterface", + [ASTDeclaratorDeclInterface]> { + let methods = [ + InterfaceMethod<"", "bool", "isOverloadedOperator", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->isOverloadedOperator(); + }] + >, + InterfaceMethod<"", "bool", "isStatic", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->isStatic(); + }] + > + ]; + } + + def ASTCXXMethodDeclInterface : AttrInterface<"ASTCXXMethodDeclInterface", + [ASTFunctionDeclInterface]> { + let methods = [ + InterfaceMethod<"", "bool", "isCopyAssignmentOperator", (ins), [{}], + /*defaultImplementation=*/ [{ + if (auto decl = dyn_cast($_attr.getAstDecl())) + return decl->isCopyAssignmentOperator(); + return false; + }] + >, + InterfaceMethod<"", "bool", "isMoveAssignmentOperator", (ins), [{}], + /*defaultImplementation=*/ [{ + if (auto decl = dyn_cast($_attr.getAstDecl())) + return decl->isMoveAssignmentOperator(); + return false; + }] + >, + InterfaceMethod<"", "bool", "isConst", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->isConst(); + }] + > + ]; + } + + def ASTCXXConstructorDeclInterface : AttrInterface<"ASTCXXConstructorDeclInterface", + [ASTCXXMethodDeclInterface]> { + let methods = [ + InterfaceMethod<"", "bool", "isDefaultConstructor", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->isDefaultConstructor(); + }] + >, + InterfaceMethod<"", "bool", "isCopyConstructor", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->isCopyConstructor(); + }] + > + ]; + } + + def ASTCXXConversionDeclInterface : AttrInterface<"ASTCXXConversionDeclInterface", + [ASTCXXMethodDeclInterface]>; + + def ASTCXXDestructorDeclInterface : AttrInterface<"ASTCXXDestructorDeclInterface", + [ASTCXXMethodDeclInterface]>; + + def ASTTypeDeclInterface : AttrInterface<"ASTTypeDeclInterface", + [ASTNamedDeclInterface]>; + + def ASTTagDeclInterface : AttrInterface<"ASTTagDeclInterface", + [ASTTypeDeclInterface]> { + let methods = [ + InterfaceMethod<"", "clang::TagTypeKind", "getTagKind", (ins), [{}], + /*defaultImplementation=*/ [{ + return $_attr.getAstDecl()->getTagKind(); + }] + > + ]; + } + + def ASTRecordDeclInterface : AttrInterface<"ASTRecordDeclInterface", + [ASTTagDeclInterface]> { + let methods = [ + InterfaceMethod<"", "bool", "isLambda", (ins), [{}], + /*defaultImplementation=*/ [{ + if (auto ast = clang::dyn_cast($_attr.getAstDecl())) + return ast->isLambda(); + return false; + }] + >, + InterfaceMethod<"", "bool", "hasPromiseType", (ins), [{}], + /*defaultImplementation=*/ [{ + if (!clang::isa($_attr.getAstDecl())) + return false; + for (const auto *sub : $_attr.getAstDecl()->decls()) { + if (auto subRec = clang::dyn_cast(sub)) { + if (subRec->getDeclName().isIdentifier() && + subRec->getName() == "promise_type") { + return true; + } + } + } + return false; + }] + > + ]; + } + + def AnyASTFunctionDeclAttr : Attr< + CPred<"::mlir::isa<::mlir::cir::ASTFunctionDeclInterface>($_self)">, + "AST Function attribute"> { + let storageType = "::mlir::Attribute"; + let returnType = "::mlir::Attribute"; + let convertFromStorage = "$_self"; + let constBuilderCall = "$0"; + } + +} // namespace mlir::cir + +#endif // MLIR_CIR_INTERFACES_AST_ATTR_INTERFACES diff --git a/clang/include/clang/CIR/Interfaces/CMakeLists.txt b/clang/include/clang/CIR/Interfaces/CMakeLists.txt new file mode 100644 index 000000000000..6925b69a2c97 --- /dev/null +++ b/clang/include/clang/CIR/Interfaces/CMakeLists.txt @@ -0,0 +1,15 @@ +# This replicates part of the add_mlir_interface cmake function from MLIR that +# cannot be used here. This happens because it expects to be run inside MLIR +# directory which is not the case for CIR (and also FIR, both have similar +# workarounds). + +# Declare a dialect in the include directory +function(add_clang_mlir_attr_interface interface) + set(LLVM_TARGET_DEFINITIONS ${interface}.td) + mlir_tablegen(${interface}.h.inc -gen-attr-interface-decls) + mlir_tablegen(${interface}.cpp.inc -gen-attr-interface-defs) + add_public_tablegen_target(MLIRCIR${interface}IncGen) + add_dependencies(mlir-generic-headers MLIRCIR${interface}IncGen) +endfunction() + +add_clang_mlir_attr_interface(ASTAttrInterfaces) diff --git a/clang/include/clang/CIR/Passes.h b/clang/include/clang/CIR/Passes.h new file mode 100644 index 000000000000..293af0412e6d --- /dev/null +++ b/clang/include/clang/CIR/Passes.h @@ -0,0 +1,34 @@ +//===- Passes.h - CIR Passes Definition -----------------------------------===// +// +// 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 exposes the entry points to create compiler passes for ClangIR. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_CIR_PASSES_H +#define CLANG_CIR_PASSES_H + +#include "mlir/Pass/Pass.h" + +#include + +namespace cir { +/// Create a pass for lowering from MLIR builtin dialects such as `Affine` and +/// `Std`, to the LLVM dialect for codegen. +std::unique_ptr createConvertMLIRToLLVMPass(); + +/// Create a pass that fully lowers CIR to the MLIR in-tree dialects. +std::unique_ptr createConvertCIRToMLIRPass(); + +namespace direct { +/// Create a pass that fully lowers CIR to the LLVMIR dialect. +std::unique_ptr createConvertCIRToLLVMPass(); +} // namespace direct +} // end namespace cir + +#endif // CLANG_CIR_PASSES_H diff --git a/clang/include/clang/CIRFrontendAction/CIRGenAction.h b/clang/include/clang/CIRFrontendAction/CIRGenAction.h new file mode 100644 index 000000000000..d61c90573ade --- /dev/null +++ b/clang/include/clang/CIRFrontendAction/CIRGenAction.h @@ -0,0 +1,117 @@ +//===---- CIRGenAction.h - CIR Code Generation Frontend Action -*- C++ -*--===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CIR_CIRGENACTION_H +#define LLVM_CLANG_CIR_CIRGENACTION_H + +#include "clang/Frontend/FrontendAction.h" +#include + +namespace llvm { +class LLVMIRContext; +class Module; +} // namespace llvm + +namespace mlir { +class MLIRContext; +class ModuleOp; +template class OwningOpRef; +} // namespace mlir + +namespace cir { +class CIRGenConsumer; +class CIRGenerator; + +class CIRGenAction : public clang::ASTFrontendAction { +public: + enum class OutputType { + EmitAssembly, + EmitCIR, + EmitLLVM, + EmitMLIR, + EmitObj, + None + }; + +private: + friend class CIRGenConsumer; + + // TODO: this is redundant but just using the OwningModuleRef requires more of + // clang against MLIR. Hide this somewhere else. + std::unique_ptr> mlirModule; + std::unique_ptr llvmModule; + + mlir::MLIRContext *mlirContext; + + mlir::OwningOpRef loadModule(llvm::MemoryBufferRef mbRef); + +protected: + CIRGenAction(OutputType action, mlir::MLIRContext *_MLIRContext = nullptr); + + std::unique_ptr + CreateASTConsumer(clang::CompilerInstance &CI, + llvm::StringRef InFile) override; + + void ExecuteAction() override; + + void EndSourceFileAction() override; + +public: + ~CIRGenAction() override; + + virtual bool hasCIRSupport() const override { return true; } + + CIRGenConsumer *cgConsumer; + OutputType action; +}; + +class EmitCIRAction : public CIRGenAction { + virtual void anchor(); + +public: + EmitCIRAction(mlir::MLIRContext *mlirCtx = nullptr); +}; + +class EmitCIROnlyAction : public CIRGenAction { + virtual void anchor(); + +public: + EmitCIROnlyAction(mlir::MLIRContext *mlirCtx = nullptr); +}; + +class EmitMLIRAction : public CIRGenAction { + virtual void anchor(); + +public: + EmitMLIRAction(mlir::MLIRContext *mlirCtx = nullptr); +}; + +class EmitLLVMAction : public CIRGenAction { + virtual void anchor(); + +public: + EmitLLVMAction(mlir::MLIRContext *mlirCtx = nullptr); +}; + +class EmitAssemblyAction : public CIRGenAction { + virtual void anchor(); + +public: + EmitAssemblyAction(mlir::MLIRContext *mlirCtx = nullptr); +}; + +class EmitObjAction : public CIRGenAction { + virtual void anchor(); + +public: + EmitObjAction(mlir::MLIRContext *mlirCtx = nullptr); +}; + +} // namespace cir + +#endif diff --git a/clang/include/clang/CMakeLists.txt b/clang/include/clang/CMakeLists.txt index 0dc9ea5ed8ac..5d376794899d 100644 --- a/clang/include/clang/CMakeLists.txt +++ b/clang/include/clang/CMakeLists.txt @@ -1,5 +1,6 @@ add_subdirectory(AST) add_subdirectory(Basic) +add_subdirectory(CIR) add_subdirectory(Driver) add_subdirectory(Parse) add_subdirectory(Sema) diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td index b32bcc68c0c2..0769c160b91f 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1105,6 +1105,10 @@ def emit_ast : Flag<["-"], "emit-ast">, Flags<[CoreOption]>, HelpText<"Emit Clang AST files for source inputs">; def emit_llvm : Flag<["-"], "emit-llvm">, Flags<[CC1Option, FC1Option, FlangOption]>, Group, HelpText<"Use the LLVM representation for assembler and object files">; +def emit_cir : Flag<["-"], "emit-cir">, Flags<[CoreOption, CC1Option]>, + Group, HelpText<"Build ASTs and then lower to ClangIR, emit the .cir file">; +def emit_mlir : Flag<["-"], "emit-mlir">, Flags<[CC1Option]>, Group, + HelpText<"Build ASTs and then lower through ClangIR to MLIR, emit the .milr file">; def emit_interface_stubs : Flag<["-"], "emit-interface-stubs">, Flags<[CC1Option]>, Group, HelpText<"Generate Interface Stub Files.">; def emit_merged_ifs : Flag<["-"], "emit-merged-ifs">, @@ -2150,6 +2154,45 @@ def flto_EQ : Joined<["-"], "flto=">, Flags<[CoreOption, CC1Option]>, Group, Values<"thin,full">; def flto_EQ_jobserver : Flag<["-"], "flto=jobserver">, Group, Alias, AliasArgs<["full"]>, HelpText<"Enable LTO in 'full' mode">; +def fclangir_enable : Flag<["-"], "fclangir-enable">, Flags<[CoreOption, CC1Option]>, + Group, HelpText<"Use ClangIR pipeline to compile">, + MarshallingInfoFlag>; +def clangir_disable_passes : Flag<["-"], "clangir-disable-passes">, + Flags<[CoreOption, CC1Option]>, + HelpText<"Disable CIR transformations pipeline">, + MarshallingInfoFlag>; +def clangir_disable_verifier : Flag<["-"], "clangir-disable-verifier">, + Flags<[CoreOption, CC1Option]>, + HelpText<"ClangIR: Disable MLIR module verifier">, + MarshallingInfoFlag>; +def clangir_disable_emit_cxx_default : Flag<["-"], "clangir-disable-emit-cxx-default">, + Flags<[CoreOption, CC1Option]>, + HelpText<"ClangIR: Disable emission of c++ default (compiler implemented) methods.">, + MarshallingInfoFlag>; +def fclangir_disable_deferred_EQ : Joined<["-"], "fclangir-build-deferred-threshold=">, + Flags<[CoreOption, CC1Option]>, Group, + HelpText<"ClangIR (internal): Control the recursion level for calls to buildDeferred (defaults to 500)">, + MarshallingInfoInt, "500u">; +def fclangir_skip_system_headers : Joined<["-"], "fclangir-skip-system-headers">, + Flags<[CoreOption, CC1Option]>, Group, + HelpText<"ClangIR (internal): buildDeferred skip functions defined in system headers">, + MarshallingInfoFlag>; +def clangir_verify_diagnostics : Flag<["-"], "clangir-verify-diagnostics">, + Flags<[CoreOption, CC1Option]>, + HelpText<"ClangIR: Enable diagnostic verification in MLIR, similar to clang's -verify">, + MarshallingInfoFlag>; +def fclangir_lifetime_check_EQ : Joined<["-"], "fclangir-lifetime-check=">, + Flags<[CoreOption, CC1Option]>, Group, + HelpText<"Run lifetime checker">, + MarshallingInfoString>; +def fclangir_lifetime_check : Flag<["-"], "fclangir-lifetime-check">, + Flags<[CoreOption, CC1Option]>, Group, + Alias, AliasArgs<["history=invalid,null"]>, + HelpText<"Run lifetime checker">; +defm clangir_direct_lowering : BoolFOption<"clangir-direct-lowering", + FrontendOpts<"ClangIRDirectLowering">, DefaultTrue, + PosFlag, + NegFlag>; def flto_EQ_auto : Flag<["-"], "flto=auto">, Group, Alias, AliasArgs<["full"]>, HelpText<"Enable LTO in 'full' mode">; def flto : Flag<["-"], "flto">, Flags<[CoreOption, CC1Option]>, Group, @@ -4986,8 +5029,8 @@ def fno_reformat : Flag<["-"], "fno-reformat">, Group, HelpText<"Dump the cooked character stream in -E mode">; defm analyzed_objects_for_unparse : OptOutFC1FFlag<"analyzed-objects-for-unparse", "", "Do not use the analyzed objects when unparsing">; -def emit_mlir : Flag<["-"], "emit-mlir">, Group, - HelpText<"Build the parse tree, then lower it to MLIR">; +//def emit_mlir : Flag<["-"], "emit-mlir">, Group, +// HelpText<"Build the parse tree, then lower it to MLIR">; def emit_fir : Flag<["-"], "emit-fir">, Alias; } // let Flags = [FC1Option, FlangOnlyOption] @@ -5844,6 +5887,8 @@ def ast_dump_lookups : Flag<["-"], "ast-dump-lookups">, MarshallingInfoFlag>; def ast_view : Flag<["-"], "ast-view">, HelpText<"Build ASTs and view them with GraphViz">; +def emit_cir_only : Flag<["-"], "emit-cir-only">, + HelpText<"Build ASTs and convert to CIR, discarding output">; def emit_module : Flag<["-"], "emit-module">, HelpText<"Generate pre-compiled module file from a module map">; def emit_module_interface : Flag<["-"], "emit-module-interface">, diff --git a/clang/include/clang/Frontend/FrontendAction.h b/clang/include/clang/Frontend/FrontendAction.h index 039f6f247b6d..effc505d9a3e 100644 --- a/clang/include/clang/Frontend/FrontendAction.h +++ b/clang/include/clang/Frontend/FrontendAction.h @@ -196,6 +196,9 @@ public: /// Does this action support use with IR files? virtual bool hasIRSupport() const { return false; } + /// Does this action support use with CIR files? + virtual bool hasCIRSupport() const { return false; } + /// Does this action support use with code completion? virtual bool hasCodeCompletionSupport() const { return false; } diff --git a/clang/include/clang/Frontend/FrontendOptions.h b/clang/include/clang/Frontend/FrontendOptions.h index d5b13df5be16..51cb71c05c3b 100644 --- a/clang/include/clang/Frontend/FrontendOptions.h +++ b/clang/include/clang/Frontend/FrontendOptions.h @@ -63,6 +63,15 @@ enum ActionKind { /// Translate input source into HTML. EmitHTML, + /// Emit a .cir file + EmitCIR, + + /// Generate CIR, bud don't emit anything. + EmitCIROnly, + + /// Emit a .mlir file + EmitMLIR, + /// Emit a .ll file. EmitLLVM, @@ -352,6 +361,27 @@ public: /// Output (and read) PCM files regardless of compiler errors. unsigned AllowPCMWithCompilerErrors : 1; + /// Use Clang IR pipeline to emit code + unsigned UseClangIRPipeline : 1; + + /// Lower directly from ClangIR to LLVM + unsigned ClangIRDirectLowering : 1; + + /// Disable Clang IR specific (CIR) passes + unsigned ClangIRDisablePasses : 1; + + /// Disable Clang IR (CIR) verifier + unsigned ClangIRDisableCIRVerifier : 1; + + /// Disable ClangIR emission for CXX default (compiler generated methods). + unsigned ClangIRDisableEmitCXXDefault : 1; + + /// Enable diagnostic verification for CIR + unsigned ClangIRVerifyDiags : 1; + + // Enable Clang IR based lifetime check + unsigned ClangIRLifetimeCheck : 1; + CodeCompleteOptions CodeCompleteOpts; /// Specifies the output format of the AST. @@ -423,6 +453,8 @@ public: std::string MTMigrateDir; std::string ARCMTMigrateReportOut; + std::string ClangIRLifetimeCheckOpts; + /// The input kind, either specified via -x argument or deduced from the input /// file name. InputKind DashX; @@ -485,6 +517,10 @@ public: /// should only be used for debugging and experimental features. std::vector LLVMArgs; + /// A list of arguments to forward to MLIR's option processing; this + /// should only be used for debugging and experimental features. + std::vector MLIRArgs; + /// File name of the file that will provide record layouts /// (in the format produced by -fdump-record-layouts). std::string OverrideRecordLayoutsFile; diff --git a/clang/lib/Basic/LangStandards.cpp b/clang/lib/Basic/LangStandards.cpp index 511bb953faaa..a19432ebb077 100644 --- a/clang/lib/Basic/LangStandards.cpp +++ b/clang/lib/Basic/LangStandards.cpp @@ -49,6 +49,7 @@ LangStandard::Kind clang::getDefaultLanguageStandard(clang::Language Lang, switch (Lang) { case Language::Unknown: case Language::LLVM_IR: + case Language::CIR: llvm_unreachable("Invalid input kind!"); case Language::OpenCL: return LangStandard::lang_opencl12; diff --git a/clang/lib/CIR/CMakeLists.txt b/clang/lib/CIR/CMakeLists.txt new file mode 100644 index 000000000000..f3ef8525e15c --- /dev/null +++ b/clang/lib/CIR/CMakeLists.txt @@ -0,0 +1,7 @@ +include_directories(${LLVM_MAIN_SRC_DIR}/../mlir/include) +include_directories(${CMAKE_BINARY_DIR}/tools/mlir/include) + +add_subdirectory(Dialect) +add_subdirectory(CodeGen) +add_subdirectory(FrontendAction) +add_subdirectory(Interfaces) diff --git a/clang/lib/CIR/CodeGen/ABIInfo.h b/clang/lib/CIR/CodeGen/ABIInfo.h new file mode 100644 index 000000000000..5a2e3ff56ca4 --- /dev/null +++ b/clang/lib/CIR/CodeGen/ABIInfo.h @@ -0,0 +1,45 @@ +//===----- ABIInfo.h - ABI information access & encapsulation ---*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_ABIINFO_H +#define LLVM_CLANG_LIB_CIR_ABIINFO_H + +#include "clang/AST/Type.h" + +namespace cir { + +class ABIArgInfo; +class CIRGenCXXABI; +class CIRGenFunctionInfo; +class CIRGenTypes; + +/// ABIInfo - Target specific hooks for defining how a type should be passed or +/// returned from functions. +class ABIInfo { + ABIInfo() = delete; + +public: + CIRGenTypes &CGT; + + ABIInfo(CIRGenTypes &cgt) : CGT{cgt} {} + + virtual ~ABIInfo(); + + CIRGenCXXABI &getCXXABI() const; + clang::ASTContext &getContext() const; + + virtual void computeInfo(CIRGenFunctionInfo &FI) const = 0; + + // Implement the Type::IsPromotableIntegerType for ABI specific needs. The + // only difference is that this consideres bit-precise integer types as well. + bool isPromotableIntegerTypeForABI(clang::QualType Ty) const; +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/Address.h b/clang/lib/CIR/CodeGen/Address.h new file mode 100644 index 000000000000..31186f4a8e1f --- /dev/null +++ b/clang/lib/CIR/CodeGen/Address.h @@ -0,0 +1,117 @@ +//===-- Address.h - An aligned address -------------------------*- C++ -*-===// +// +// 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 LLVM_CLANG_LIB_CIR_ADDRESS_H +#define LLVM_CLANG_LIB_CIR_ADDRESS_H + +#include "clang/AST/CharUnits.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/IR/Constants.h" + +#include "mlir/IR/Value.h" + +namespace cir { + +// Indicates whether a pointer is known not to be null. +enum KnownNonNull_t { NotKnownNonNull, KnownNonNull }; + +class Address { + llvm::PointerIntPair PointerAndKnownNonNull; + mlir::Type ElementType; + clang::CharUnits Alignment; + +protected: + Address(std::nullptr_t) : ElementType(nullptr) {} + +public: + Address(mlir::Value pointer, mlir::Type elementType, + clang::CharUnits alignment, + KnownNonNull_t IsKnownNonNull = NotKnownNonNull) + : PointerAndKnownNonNull(pointer, IsKnownNonNull), + ElementType(elementType), Alignment(alignment) { + assert(pointer.getType().isa() && + "Expected cir.ptr type"); + + assert(pointer && "Pointer cannot be null"); + assert(elementType && "Element type cannot be null"); + assert(!alignment.isZero() && "Alignment cannot be zero"); + } + Address(mlir::Value pointer, clang::CharUnits alignment) + : Address(pointer, + pointer.getType().cast().getPointee(), + alignment) { + + assert((!alignment.isZero() || pointer == nullptr) && + "creating valid address with invalid alignment"); + } + + static Address invalid() { return Address(nullptr); } + bool isValid() const { + return PointerAndKnownNonNull.getPointer() != nullptr; + } + + /// Return address with different pointer, but same element type and + /// alignment. + Address withPointer(mlir::Value NewPointer, + KnownNonNull_t IsKnownNonNull) const { + return Address(NewPointer, getElementType(), getAlignment(), + IsKnownNonNull); + } + + /// Return address with different alignment, but same pointer and element + /// type. + Address withAlignment(clang::CharUnits NewAlignment) const { + return Address(getPointer(), getElementType(), NewAlignment, + isKnownNonNull()); + } + + /// Return address with different element type, but same pointer and + /// alignment. + Address withElementType(mlir::Type ElemTy) const { + return Address(getPointer(), ElemTy, getAlignment(), isKnownNonNull()); + } + + mlir::Value getPointer() const { + assert(isValid()); + return PointerAndKnownNonNull.getPointer(); + } + + /// Return the alignment of this pointer. + clang::CharUnits getAlignment() const { + // assert(isValid()); + return Alignment; + } + + mlir::Type getElementType() const { + assert(isValid()); + return ElementType; + } + + /// Whether the pointer is known not to be null. + KnownNonNull_t isKnownNonNull() const { + assert(isValid()); + return (KnownNonNull_t)PointerAndKnownNonNull.getInt(); + } + + /// Set the non-null bit. + Address setKnownNonNull() { + assert(isValid()); + PointerAndKnownNonNull.setInt(true); + return *this; + } +}; + +} // namespace cir + +#endif // LLVM_CLANG_LIB_CIR_ADDRESS_H diff --git a/clang/lib/CIR/CodeGen/CIRDataLayout.h b/clang/lib/CIR/CodeGen/CIRDataLayout.h new file mode 100644 index 000000000000..b1b10ba6b6da --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRDataLayout.h @@ -0,0 +1,82 @@ +//===--- CIRDataLayout.h - CIR Data Layout Information ----------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// Provides a LLVM-like API wrapper to DLTI and MLIR layout queries. This makes +// it easier to port some of LLVM codegen layout logic to CIR. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CIRDATALAYOUT_H +#define LLVM_CLANG_LIB_CIR_CIRDATALAYOUT_H + +#include "UnimplementedFeatureGuarding.h" +#include "mlir/Dialect/DLTI/DLTI.h" +#include "mlir/IR/BuiltinOps.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +namespace cir { + +class CIRDataLayout { + bool bigEndian = false; + +public: + mlir::DataLayout layout; + + CIRDataLayout(mlir::ModuleOp modOp); + bool isBigEndian() { return bigEndian; } + + // `useABI` is `true` if not using prefered alignment. + unsigned getAlignment(mlir::Type ty, bool useABI) const { + return useABI ? layout.getTypeABIAlignment(ty) + : layout.getTypePreferredAlignment(ty); + } + unsigned getABITypeAlign(mlir::Type ty) const { + return getAlignment(ty, true); + } + + /// Returns the maximum number of bytes that may be overwritten by + /// storing the specified type. + /// + /// If Ty is a scalable vector type, the scalable property will be set and + /// the runtime size will be a positive integer multiple of the base size. + /// + /// For example, returns 5 for i36 and 10 for x86_fp80. + unsigned getTypeStoreSize(mlir::Type Ty) const { + // FIXME: this is a bit inaccurate, see DataLayout::getTypeStoreSize for + // more information. + return llvm::divideCeil(layout.getTypeSizeInBits(Ty), 8); + } + + /// Returns the offset in bytes between successive objects of the + /// specified type, including alignment padding. + /// + /// If Ty is a scalable vector type, the scalable property will be set and + /// the runtime size will be a positive integer multiple of the base size. + /// + /// This is the amount that alloca reserves for this type. For example, + /// returns 12 or 16 for x86_fp80, depending on alignment. + unsigned getTypeAllocSize(mlir::Type Ty) const { + // Round up to the next alignment boundary. + return llvm::alignTo(getTypeStoreSize(Ty), layout.getTypeABIAlignment(Ty)); + } + + unsigned getPointerTypeSizeInBits(mlir::Type Ty) const { + assert(Ty.isa() && + "This should only be called with a pointer type"); + return layout.getTypeSizeInBits(Ty); + } + + mlir::Type getIntPtrType(mlir::Type Ty) const { + assert(Ty.isa() && "Expected pointer type"); + auto IntTy = mlir::cir::IntType::get(Ty.getContext(), + getPointerTypeSizeInBits(Ty), false); + return IntTy; + } +}; + +} // namespace cir + +#endif \ No newline at end of file diff --git a/clang/lib/CIR/CodeGen/CIRGenBuilder.h b/clang/lib/CIR/CodeGen/CIRGenBuilder.h new file mode 100644 index 000000000000..d451c40ae901 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenBuilder.h @@ -0,0 +1,806 @@ +//===-- CIRGenBuilder.h - CIRBuilder implementation ------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CIRGENBUILDER_H +#define LLVM_CLANG_LIB_CIR_CIRGENBUILDER_H + +#include "Address.h" +#include "CIRGenTypeCache.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/Decl.h" +#include "clang/AST/Type.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "clang/CIR/Dialect/IR/FPEnv.h" + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/Types.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/FloatingPointMode.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringSet.h" +#include "llvm/Support/ErrorHandling.h" +#include +#include +#include + +namespace cir { + +class CIRGenFunction; + +class CIRGenBuilderTy : public mlir::OpBuilder { + const CIRGenTypeCache &typeCache; + bool IsFPConstrained = false; + fp::ExceptionBehavior DefaultConstrainedExcept = fp::ebStrict; + llvm::RoundingMode DefaultConstrainedRounding = llvm::RoundingMode::Dynamic; + + llvm::StringMap GlobalsVersioning; + llvm::StringSet<> anonRecordNames; + +public: + CIRGenBuilderTy(mlir::MLIRContext &C, const CIRGenTypeCache &tc) + : mlir::OpBuilder(&C), typeCache(tc) {} + + std::string getUniqueAnonRecordName() { + std::string name = "anon." + std::to_string(anonRecordNames.size()); + anonRecordNames.insert(name); + return name; + } + + // + // Floating point specific helpers + // ------------------------------- + // + + /// Enable/Disable use of constrained floating point math. When enabled the + /// CreateF() calls instead create constrained floating point intrinsic + /// calls. Fast math flags are unaffected by this setting. + void setIsFPConstrained(bool IsCon) { + if (IsCon) + llvm_unreachable("Constrained FP NYI"); + IsFPConstrained = IsCon; + } + + /// Query for the use of constrained floating point math + bool getIsFPConstrained() { + if (IsFPConstrained) + llvm_unreachable("Constrained FP NYI"); + return IsFPConstrained; + } + + /// Set the exception handling to be used with constrained floating point + void setDefaultConstrainedExcept(fp::ExceptionBehavior NewExcept) { +#ifndef NDEBUG + std::optional ExceptStr = + convertExceptionBehaviorToStr(NewExcept); + assert(ExceptStr && "Garbage strict exception behavior!"); +#endif + DefaultConstrainedExcept = NewExcept; + } + + /// Set the rounding mode handling to be used with constrained floating point + void setDefaultConstrainedRounding(llvm::RoundingMode NewRounding) { +#ifndef NDEBUG + std::optional RoundingStr = + convertRoundingModeToStr(NewRounding); + assert(RoundingStr && "Garbage strict rounding mode!"); +#endif + DefaultConstrainedRounding = NewRounding; + } + + /// Get the exception handling used with constrained floating point + fp::ExceptionBehavior getDefaultConstrainedExcept() { + return DefaultConstrainedExcept; + } + + /// Get the rounding mode handling used with constrained floating point + llvm::RoundingMode getDefaultConstrainedRounding() { + return DefaultConstrainedRounding; + } + + // + // Attribute helpers + // ----------------- + // + + /// Get constant address of a global variable as an MLIR attribute. + /// This wrapper infers the attribute type through the global op. + mlir::cir::GlobalViewAttr getGlobalViewAttr(mlir::cir::GlobalOp globalOp, + mlir::ArrayAttr indices = {}) { + auto type = getPointerTo(globalOp.getSymType()); + return getGlobalViewAttr(type, globalOp, indices); + } + + /// Get constant address of a global variable as an MLIR attribute. + mlir::cir::GlobalViewAttr getGlobalViewAttr(mlir::cir::PointerType type, + mlir::cir::GlobalOp globalOp, + mlir::ArrayAttr indices = {}) { + auto symbol = mlir::FlatSymbolRefAttr::get(globalOp.getSymNameAttr()); + return mlir::cir::GlobalViewAttr::get(type, symbol, indices); + } + + mlir::TypedAttr getZeroAttr(mlir::Type t) { + return mlir::cir::ZeroAttr::get(getContext(), t); + } + + mlir::cir::BoolAttr getCIRBoolAttr(bool state) { + return mlir::cir::BoolAttr::get(getContext(), getBoolTy(), state); + } + + mlir::TypedAttr getConstPtrAttr(mlir::Type t, uint64_t v) { + assert(t.isa() && "expected cir.ptr"); + return mlir::cir::ConstPtrAttr::get(getContext(), t, v); + } + + mlir::cir::ConstArrayAttr getString(llvm::StringRef str, mlir::Type eltTy, + unsigned size = 0) { + unsigned finalSize = size ? size : str.size(); + auto arrayTy = mlir::cir::ArrayType::get(getContext(), eltTy, finalSize); + return getConstArray(mlir::StringAttr::get(str, arrayTy), arrayTy); + } + + mlir::cir::ConstArrayAttr getConstArray(mlir::Attribute attrs, + mlir::cir::ArrayType arrayTy) { + return mlir::cir::ConstArrayAttr::get(arrayTy, attrs); + } + + mlir::Attribute getConstStructOrZeroAttr(mlir::ArrayAttr arrayAttr, + bool packed = false, + mlir::Type type = {}) { + llvm::SmallVector members; + auto structTy = type.dyn_cast(); + assert(structTy && "expected cir.struct"); + assert(!packed && "unpacked struct is NYI"); + + // Collect members and check if they are all zero. + bool isZero = true; + for (auto &attr : arrayAttr) { + const auto typedAttr = attr.dyn_cast(); + members.push_back(typedAttr.getType()); + isZero &= isNullValue(typedAttr); + } + + // Struct type not specified: create type from members. + if (!structTy) + structTy = getType( + members, mlir::StringAttr::get(getContext()), + /*incomplete=*/false, packed, mlir::cir::StructType::Struct, + /*ast=*/nullptr); + + // Return zero or anonymous constant struct. + if (isZero) + return mlir::cir::ZeroAttr::get(getContext(), structTy); + return mlir::cir::ConstStructAttr::get(structTy, arrayAttr); + } + + mlir::cir::ConstStructAttr getAnonConstStruct(mlir::ArrayAttr arrayAttr, + bool packed = false, + mlir::Type ty = {}) { + assert(!packed && "NYI"); + llvm::SmallVector members; + for (auto &f : arrayAttr) { + auto ta = f.dyn_cast(); + assert(ta && "expected typed attribute member"); + members.push_back(ta.getType()); + } + + if (!ty) + ty = getAnonStructTy(members, /*incomplete=*/false, packed); + + auto sTy = ty.dyn_cast(); + assert(sTy && "expected struct type"); + return mlir::cir::ConstStructAttr::get(sTy, arrayAttr); + } + + mlir::cir::TypeInfoAttr getTypeInfo(mlir::ArrayAttr fieldsAttr) { + auto anonStruct = getAnonConstStruct(fieldsAttr); + return mlir::cir::TypeInfoAttr::get(anonStruct.getType(), fieldsAttr); + } + + mlir::TypedAttr getZeroInitAttr(mlir::Type ty) { + if (ty.isa()) + return mlir::cir::IntAttr::get(ty, 0); + if (ty.isa()) + return mlir::FloatAttr::get(ty, 0.0); + if (auto arrTy = ty.dyn_cast()) + return getZeroAttr(arrTy); + if (auto ptrTy = ty.dyn_cast()) + return getConstPtrAttr(ptrTy, 0); + if (auto structTy = ty.dyn_cast()) + return getZeroAttr(structTy); + llvm_unreachable("Zero initializer for given type is NYI"); + } + + // TODO(cir): Once we have CIR float types, replace this by something like a + // NullableValueInterface to allow for type-independent queries. + bool isNullValue(mlir::Attribute attr) const { + if (attr.isa()) + return true; + if (const auto ptrVal = attr.dyn_cast()) + return ptrVal.isNullValue(); + + if (attr.isa()) + return false; + + // TODO(cir): introduce char type in CIR and check for that instead. + if (const auto intVal = attr.dyn_cast()) + return intVal.isNullValue(); + + if (const auto fpVal = attr.dyn_cast()) { + bool ignored; + llvm::APFloat FV(+0.0); + FV.convert(fpVal.getValue().getSemantics(), + llvm::APFloat::rmNearestTiesToEven, &ignored); + return FV.bitwiseIsEqual(fpVal.getValue()); + } + + if (const auto structVal = attr.dyn_cast()) { + for (const auto elt : structVal.getMembers()) { + // FIXME(cir): the struct's ID should not be considered a member. + if (elt.isa()) + continue; + if (!isNullValue(elt)) + return false; + } + return true; + } + + if (const auto arrayVal = attr.dyn_cast()) { + if (arrayVal.getElts().isa()) + return false; + for (const auto elt : arrayVal.getElts().cast()) { + if (!isNullValue(elt)) + return false; + } + return true; + } + + llvm_unreachable("NYI"); + } + + // + // Type helpers + // ------------ + // + mlir::cir::IntType getUIntNTy(int N) { + switch (N) { + case 8: + return getUInt8Ty(); + case 16: + return getUInt16Ty(); + case 32: + return getUInt32Ty(); + case 64: + return getUInt64Ty(); + default: + llvm_unreachable("Unknown bit-width"); + } + } + + mlir::cir::IntType getSIntNTy(int N) { + switch (N) { + case 8: + return getSInt8Ty(); + case 16: + return getSInt16Ty(); + case 32: + return getSInt32Ty(); + case 64: + return getSInt64Ty(); + default: + llvm_unreachable("Unknown bit-width"); + } + } + + mlir::cir::VoidType getVoidTy() { return typeCache.VoidTy; } + + mlir::cir::IntType getSInt8Ty() { return typeCache.SInt8Ty; } + mlir::cir::IntType getSInt16Ty() { return typeCache.SInt16Ty; } + mlir::cir::IntType getSInt32Ty() { return typeCache.SInt32Ty; } + mlir::cir::IntType getSInt64Ty() { return typeCache.SInt64Ty; } + + mlir::cir::IntType getUInt8Ty() { return typeCache.UInt8Ty; } + mlir::cir::IntType getUInt16Ty() { return typeCache.UInt16Ty; } + mlir::cir::IntType getUInt32Ty() { return typeCache.UInt32Ty; } + mlir::cir::IntType getUInt64Ty() { return typeCache.UInt64Ty; } + + bool isInt8Ty(mlir::Type i) { + return i == typeCache.UInt8Ty || i == typeCache.SInt8Ty; + } + bool isInt16Ty(mlir::Type i) { + return i == typeCache.UInt16Ty || i == typeCache.SInt16Ty; + } + bool isInt32Ty(mlir::Type i) { + return i == typeCache.UInt32Ty || i == typeCache.SInt32Ty; + } + bool isInt64Ty(mlir::Type i) { + return i == typeCache.UInt64Ty || i == typeCache.SInt64Ty; + } + bool isInt(mlir::Type i) { return i.isa(); } + + mlir::FloatType getLongDouble80BitsTy() const { + return typeCache.LongDouble80BitsTy; + } + + /// Get the proper floating point type for the given semantics. + mlir::FloatType getFloatTyForFormat(const llvm::fltSemantics &format, + bool useNativeHalf) const { + if (&format == &llvm::APFloat::IEEEhalf()) { + llvm_unreachable("IEEEhalf float format is NYI"); + } + + if (&format == &llvm::APFloat::BFloat()) + llvm_unreachable("BFloat float format is NYI"); + if (&format == &llvm::APFloat::IEEEsingle()) + llvm_unreachable("IEEEsingle float format is NYI"); + if (&format == &llvm::APFloat::IEEEdouble()) + llvm_unreachable("IEEEdouble float format is NYI"); + if (&format == &llvm::APFloat::IEEEquad()) + llvm_unreachable("IEEEquad float format is NYI"); + if (&format == &llvm::APFloat::PPCDoubleDouble()) + llvm_unreachable("PPCDoubleDouble float format is NYI"); + if (&format == &llvm::APFloat::x87DoubleExtended()) + return getLongDouble80BitsTy(); + + llvm_unreachable("Unknown float format!"); + } + + mlir::cir::BoolType getBoolTy() { + return ::mlir::cir::BoolType::get(getContext()); + } + mlir::Type getVirtualFnPtrType(bool isVarArg = false) { + // FIXME: replay LLVM codegen for now, perhaps add a vtable ptr special + // type so it's a bit more clear and C++ idiomatic. + auto fnTy = mlir::cir::FuncType::get({}, getUInt32Ty(), isVarArg); + assert(!UnimplementedFeature::isVarArg()); + return getPointerTo(getPointerTo(fnTy)); + } + + mlir::cir::FuncType getFuncType(llvm::ArrayRef params, + mlir::Type retTy, bool isVarArg = false) { + return mlir::cir::FuncType::get(params, retTy, isVarArg); + } + + // Fetch the type representing a pointer to unsigned int values. + mlir::cir::PointerType getUInt8PtrTy(unsigned AddrSpace = 0) { + return typeCache.UInt8PtrTy; + } + mlir::cir::PointerType getUInt32PtrTy(unsigned AddrSpace = 0) { + return mlir::cir::PointerType::get(getContext(), typeCache.UInt32Ty); + } + mlir::cir::PointerType getPointerTo(mlir::Type ty, + unsigned addressSpace = 0) { + assert(!UnimplementedFeature::addressSpace() && "NYI"); + return mlir::cir::PointerType::get(getContext(), ty); + } + + mlir::cir::PointerType getVoidPtrTy(unsigned AddrSpace = 0) { + if (AddrSpace) + llvm_unreachable("address space is NYI"); + return typeCache.VoidPtrTy; + } + + /// Get a CIR anonymous struct type. + mlir::cir::StructType + getAnonStructTy(llvm::ArrayRef members, bool incomplete, + bool packed = false, const clang::RecordDecl *ast = nullptr) { + return getStructTy(members, "", incomplete, packed, ast); + } + + /// Get a CIR record kind from a AST declaration tag. + mlir::cir::StructType::RecordKind + getRecordKind(const clang::TagTypeKind kind) { + switch (kind) { + case clang::TTK_Struct: + return mlir::cir::StructType::Struct; + case clang::TTK_Union: + return mlir::cir::StructType::Union; + case clang::TTK_Class: + return mlir::cir::StructType::Class; + case clang::TTK_Interface: + llvm_unreachable("interface records are NYI"); + case clang::TTK_Enum: + llvm_unreachable("enum records are NYI"); + } + } + + /// Get a CIR named struct type. + mlir::cir::StructType getStructTy(llvm::ArrayRef members, + llvm::StringRef name, bool incomplete, + bool packed, const clang::RecordDecl *ast) { + const auto nameAttr = getStringAttr(name); + mlir::cir::ASTRecordDeclAttr astAttr = nullptr; + auto kind = mlir::cir::StructType::RecordKind::Struct; + if (ast) { + astAttr = getAttr(ast); + kind = getRecordKind(ast->getTagKind()); + } + return mlir::cir::StructType::get(getContext(), members, nameAttr, + incomplete, packed, kind, astAttr); + } + + // + // Constant creation helpers + // ------------------------- + // + mlir::cir::ConstantOp getSInt32(uint32_t c, mlir::Location loc) { + auto sInt32Ty = getSInt32Ty(); + return create(loc, sInt32Ty, + mlir::cir::IntAttr::get(sInt32Ty, c)); + } + mlir::cir::ConstantOp getUInt32(uint32_t C, mlir::Location loc) { + auto uInt32Ty = getUInt32Ty(); + return create(loc, uInt32Ty, + mlir::cir::IntAttr::get(uInt32Ty, C)); + } + mlir::cir::ConstantOp getSInt64(uint64_t C, mlir::Location loc) { + auto sInt64Ty = getSInt64Ty(); + return create(loc, sInt64Ty, + mlir::cir::IntAttr::get(sInt64Ty, C)); + } + mlir::cir::ConstantOp getUInt64(uint64_t C, mlir::Location loc) { + auto uInt64Ty = getUInt64Ty(); + return create(loc, uInt64Ty, + mlir::cir::IntAttr::get(uInt64Ty, C)); + } + mlir::cir::ConstantOp getConstInt(mlir::Location loc, mlir::cir::IntType t, + uint64_t C) { + return create(loc, t, mlir::cir::IntAttr::get(t, C)); + } + mlir::cir::ConstantOp getConstInt(mlir::Location loc, llvm::APSInt intVal) { + bool isSigned = intVal.isSigned(); + auto width = intVal.getBitWidth(); + mlir::cir::IntType t = isSigned ? getSIntNTy(width) : getUIntNTy(width); + return getConstInt( + loc, t, isSigned ? intVal.getSExtValue() : intVal.getZExtValue()); + } + mlir::Value getConstAPInt(mlir::Location loc, mlir::Type typ, + const llvm::APInt &val) { + return create(loc, typ, + getAttr(typ, val)); + } + mlir::cir::ConstantOp getBool(bool state, mlir::Location loc) { + return create(loc, getBoolTy(), + getCIRBoolAttr(state)); + } + mlir::cir::ConstantOp getFalse(mlir::Location loc) { + return getBool(false, loc); + } + mlir::cir::ConstantOp getTrue(mlir::Location loc) { + return getBool(true, loc); + } + + // Creates constant nullptr for pointer type ty. + mlir::cir::ConstantOp getNullPtr(mlir::Type ty, mlir::Location loc) { + return create(loc, ty, getConstPtrAttr(ty, 0)); + } + + // Creates constant null value for integral type ty. + mlir::cir::ConstantOp getNullValue(mlir::Type ty, mlir::Location loc) { + if (ty.isa()) + return getNullPtr(ty, loc); + + mlir::TypedAttr attr; + if (ty.isa()) + attr = mlir::cir::IntAttr::get(ty, 0); + else + llvm_unreachable("NYI"); + + return create(loc, ty, attr); + } + + mlir::cir::ConstantOp getZero(mlir::Location loc, mlir::Type ty) { + // TODO: dispatch creation for primitive types. + assert(ty.isa() && "NYI for other types"); + return create(loc, ty, getZeroAttr(ty)); + } + + mlir::cir::ConstantOp getConstant(mlir::Location loc, mlir::TypedAttr attr) { + return create(loc, attr.getType(), attr); + } + + // + // Block handling helpers + // ---------------------- + // + OpBuilder::InsertPoint getBestAllocaInsertPoint(mlir::Block *block) { + auto lastAlloca = + std::find_if(block->rbegin(), block->rend(), [](mlir::Operation &op) { + return mlir::isa(&op); + }); + + if (lastAlloca != block->rend()) + return OpBuilder::InsertPoint(block, + ++mlir::Block::iterator(&*lastAlloca)); + return OpBuilder::InsertPoint(block, block->begin()); + }; + + // + // Operation creation helpers + // -------------------------- + // + + /// Create a copy with inferred length. + mlir::cir::CopyOp createCopy(mlir::Value dst, mlir::Value src) { + return create(dst.getLoc(), dst, src); + } + + mlir::cir::MemCpyOp createMemCpy(mlir::Location loc, mlir::Value dst, + mlir::Value src, mlir::Value len) { + return create(loc, dst, src, len); + } + + mlir::Value createNeg(mlir::Value value) { + + if (auto intTy = value.getType().dyn_cast()) { + // Source is a unsigned integer: first cast it to signed. + if (intTy.isUnsigned()) + value = createIntCast(value, getSIntNTy(intTy.getWidth())); + return create(value.getLoc(), value.getType(), + mlir::cir::UnaryOpKind::Minus, value); + } + + llvm_unreachable("negation for the given type is NYI"); + } + + // TODO: split this to createFPExt/createFPTrunc when we have dedicated cast + // operations. + mlir::Value createFloatingCast(mlir::Value v, mlir::Type destType) { + if (getIsFPConstrained()) + llvm_unreachable("constrainedfp NYI"); + + return create(v.getLoc(), destType, + mlir::cir::CastKind::floating, v); + } + + mlir::Value createFSub(mlir::Value lhs, mlir::Value rhs) { + assert(!UnimplementedFeature::metaDataNode()); + if (IsFPConstrained) + llvm_unreachable("Constrained FP NYI"); + + assert(!UnimplementedFeature::foldBinOpFMF()); + return create(lhs.getLoc(), mlir::cir::BinOpKind::Sub, + lhs, rhs); + } + + mlir::Value createPtrToBoolCast(mlir::Value v) { + return create(v.getLoc(), getBoolTy(), + mlir::cir::CastKind::ptr_to_bool, v); + } + + cir::Address createBaseClassAddr(mlir::Location loc, cir::Address addr, + mlir::Type destType) { + if (destType == addr.getElementType()) + return addr; + + auto ptrTy = getPointerTo(destType); + auto baseAddr = + create(loc, ptrTy, addr.getPointer()); + + return Address(baseAddr, ptrTy, addr.getAlignment()); + } + + // FIXME(cir): CIRGenBuilder class should have an attribute with a reference + // to the module so that we don't have search for it or pass it around. + // FIXME(cir): Track a list of globals, or at least the last one inserted, so + // that we can insert globals in the same order they are defined by CIRGen. + + /// Creates a versioned global variable. If the symbol is already taken, an ID + /// will be appended to the symbol. The returned global must always be queried + /// for its name so it can be referenced correctly. + [[nodiscard]] mlir::cir::GlobalOp + createVersionedGlobal(mlir::ModuleOp module, mlir::Location loc, + mlir::StringRef name, mlir::Type type, bool isConst, + mlir::cir::GlobalLinkageKind linkage) { + mlir::OpBuilder::InsertionGuard guard(*this); + setInsertionPointToStart(module.getBody()); + + // Create a unique name if the given name is already taken. + std::string uniqueName; + if (unsigned version = GlobalsVersioning[name.str()]++) + uniqueName = name.str() + "." + std::to_string(version); + else + uniqueName = name.str(); + + return create(loc, uniqueName, type, isConst, linkage); + } + + mlir::Value createGetGlobal(mlir::cir::GlobalOp global) { + return create( + global.getLoc(), getPointerTo(global.getSymType()), global.getName()); + } + + /// Create a pointer to a record member. + mlir::Value createGetMember(mlir::Location loc, mlir::Type result, + mlir::Value base, llvm::StringRef name, + unsigned index) { + return create(loc, result, base, name, index); + } + + /// Cast the element type of the given address to a different type, + /// preserving information like the alignment. + cir::Address createElementBitCast(mlir::Location loc, cir::Address addr, + mlir::Type destType) { + if (destType == addr.getElementType()) + return addr; + + auto ptrTy = getPointerTo(destType); + return Address(createBitcast(loc, addr.getPointer(), ptrTy), destType, + addr.getAlignment()); + } + + mlir::Value createBitcast(mlir::Location loc, mlir::Value src, + mlir::Type newTy) { + if (newTy == src.getType()) + return src; + return create(loc, newTy, mlir::cir::CastKind::bitcast, + src); + } + + mlir::Value createLoad(mlir::Location loc, Address addr) { + return create(loc, addr.getElementType(), + addr.getPointer()); + } + + mlir::Value createAlignedLoad(mlir::Location loc, mlir::Type ty, + mlir::Value ptr, + [[maybe_unused]] llvm::MaybeAlign align, + [[maybe_unused]] bool isVolatile) { + assert(!UnimplementedFeature::volatileLoadOrStore()); + assert(!UnimplementedFeature::alignedLoad()); + return create(loc, ty, ptr); + } + + mlir::Value createAlignedLoad(mlir::Location loc, mlir::Type ty, + mlir::Value ptr, llvm::MaybeAlign align) { + return createAlignedLoad(loc, ty, ptr, align, /*isVolatile=*/false); + } + + mlir::Value + createAlignedLoad(mlir::Location loc, mlir::Type ty, mlir::Value addr, + clang::CharUnits align = clang::CharUnits::One()) { + return createAlignedLoad(loc, ty, addr, align.getAsAlign()); + } + + mlir::cir::StoreOp createStore(mlir::Location loc, mlir::Value val, + Address dst) { + return create(loc, val, dst.getPointer()); + } + + mlir::cir::StoreOp createFlagStore(mlir::Location loc, bool val, + mlir::Value dst) { + auto flag = getBool(val, loc); + return create(loc, flag, dst); + } + + mlir::Value createNot(mlir::Value value) { + return create(value.getLoc(), value.getType(), + mlir::cir::UnaryOpKind::Not, value); + } + + mlir::Value createBinop(mlir::Value lhs, mlir::cir::BinOpKind kind, + const llvm::APInt &rhs) { + return create( + lhs.getLoc(), lhs.getType(), kind, lhs, + getConstAPInt(lhs.getLoc(), lhs.getType(), rhs)); + } + + mlir::Value createBinop(mlir::Value lhs, mlir::cir::BinOpKind kind, + mlir::Value rhs) { + return create(lhs.getLoc(), lhs.getType(), kind, lhs, + rhs); + } + + mlir::Value createShift(mlir::Value lhs, const llvm::APInt &rhs, + bool isShiftLeft) { + return create( + lhs.getLoc(), lhs.getType(), lhs, + getConstAPInt(lhs.getLoc(), lhs.getType(), rhs), isShiftLeft); + } + + mlir::Value createShift(mlir::Value lhs, unsigned bits, bool isShiftLeft) { + auto width = lhs.getType().dyn_cast().getWidth(); + auto shift = llvm::APInt(width, bits); + return createShift(lhs, shift, isShiftLeft); + } + + mlir::Value createShiftLeft(mlir::Value lhs, unsigned bits) { + return createShift(lhs, bits, true); + } + + mlir::Value createShiftRight(mlir::Value lhs, unsigned bits) { + return createShift(lhs, bits, false); + } + + mlir::Value createLowBitsSet(mlir::Location loc, unsigned size, + unsigned bits) { + auto val = llvm::APInt::getLowBitsSet(size, bits); + auto typ = mlir::cir::IntType::get(getContext(), size, false); + return getConstAPInt(loc, typ, val); + } + + mlir::Value createAnd(mlir::Value lhs, llvm::APInt rhs) { + auto val = getConstAPInt(lhs.getLoc(), lhs.getType(), rhs); + return createBinop(lhs, mlir::cir::BinOpKind::And, val); + } + + mlir::Value createAnd(mlir::Value lhs, mlir::Value rhs) { + return createBinop(lhs, mlir::cir::BinOpKind::And, rhs); + } + + mlir::Value createOr(mlir::Value lhs, llvm::APInt rhs) { + auto val = getConstAPInt(lhs.getLoc(), lhs.getType(), rhs); + return createBinop(lhs, mlir::cir::BinOpKind::Or, val); + } + + mlir::Value createOr(mlir::Value lhs, mlir::Value rhs) { + return createBinop(lhs, mlir::cir::BinOpKind::Or, rhs); + } + + //===--------------------------------------------------------------------===// + // Cast/Conversion Operators + //===--------------------------------------------------------------------===// + + mlir::Value createCast(mlir::cir::CastKind kind, mlir::Value src, + mlir::Type newTy) { + if (newTy == src.getType()) + return src; + return create(src.getLoc(), newTy, kind, src); + } + + mlir::Value createIntCast(mlir::Value src, mlir::Type newTy) { + return create(src.getLoc(), newTy, + mlir::cir::CastKind::integral, src); + } + + mlir::Value createIntToPtr(mlir::Value src, mlir::Type newTy) { + return create(src.getLoc(), newTy, + mlir::cir::CastKind::int_to_ptr, src); + } + + mlir::Value createPtrToInt(mlir::Value src, mlir::Type newTy) { + return create(src.getLoc(), newTy, + mlir::cir::CastKind::ptr_to_int, src); + } + + // TODO(cir): the following function was introduced to keep in sync with LLVM + // codegen. CIR does not have "zext" operations. It should eventually be + // renamed or removed. For now, we just add whatever cast is required here. + mlir::Value createZExtOrBitCast(mlir::Location loc, mlir::Value src, + mlir::Type newTy) { + auto srcTy = src.getType(); + + if (srcTy == newTy) + return src; + + if (srcTy.isa() && newTy.isa()) + return createBoolToInt(src, newTy); + + llvm_unreachable("unhandled extension cast"); + } + + mlir::Value createBoolToInt(mlir::Value src, mlir::Type newTy) { + return createCast(mlir::cir::CastKind::bool_to_int, src, newTy); + } + + mlir::Value createBitcast(mlir::Value src, mlir::Type newTy) { + return createCast(mlir::cir::CastKind::bitcast, src, newTy); + } +}; + +} // namespace cir +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp new file mode 100644 index 000000000000..825024daab8a --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp @@ -0,0 +1,691 @@ +//===---- CIRGenBuiltin.cpp - Emit CIR for builtins -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This contains code to emit Builtin calls as CIR or a function call to be +// later resolved. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenCXXABI.h" +#include "CIRGenCall.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "UnimplementedFeatureGuarding.h" + +// TODO(cir): we shouldn't need this but we currently reuse intrinsic IDs for +// convenience. +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/IR/Intrinsics.h" + +#include "clang/AST/GlobalDecl.h" +#include "clang/Basic/Builtins.h" + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/Value.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace cir; +using namespace clang; +using namespace mlir::cir; +using namespace llvm; + +static RValue buildLibraryCall(CIRGenFunction &CGF, const FunctionDecl *FD, + const CallExpr *E, + mlir::Operation *calleeValue) { + auto callee = CIRGenCallee::forDirect(calleeValue, GlobalDecl(FD)); + return CGF.buildCall(E->getCallee()->getType(), callee, E, ReturnValueSlot()); +} + +RValue CIRGenFunction::buildBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, + const CallExpr *E, + ReturnValueSlot ReturnValue) { + const FunctionDecl *FD = GD.getDecl()->getAsFunction(); + + // See if we can constant fold this builtin. If so, don't emit it at all. + // TODO: Extend this handling to all builtin calls that we can constant-fold. + Expr::EvalResult Result; + if (E->isPRValue() && E->EvaluateAsRValue(Result, CGM.getASTContext()) && + !Result.hasSideEffects()) { + llvm_unreachable("NYI"); + } + + // If current long-double semantics is IEEE 128-bit, replace math builtins + // of long-double with f128 equivalent. + // TODO: This mutation should also be applied to other targets other than PPC, + // after backend supports IEEE 128-bit style libcalls. + if (getTarget().getTriple().isPPC64() && + &getTarget().getLongDoubleFormat() == &llvm::APFloat::IEEEquad()) + llvm_unreachable("NYI"); + + // If the builtin has been declared explicitly with an assembler label, + // disable the specialized emitting below. Ideally we should communicate the + // rename in IR, or at least avoid generating the intrinsic calls that are + // likely to get lowered to the renamed library functions. + const unsigned BuiltinIDIfNoAsmLabel = + FD->hasAttr() ? 0 : BuiltinID; + + // There are LLVM math intrinsics/instructions corresponding to math library + // functions except the LLVM op will never set errno while the math library + // might. Also, math builtins have the same semantics as their math library + // twins. Thus, we can transform math library and builtin calls to their + // LLVM counterparts if the call is marked 'const' (known to never set errno). + // In case FP exceptions are enabled, the experimental versions of the + // intrinsics model those. + bool ConstWithoutErrnoAndExceptions = + getContext().BuiltinInfo.isConstWithoutErrnoAndExceptions(BuiltinID); + bool ConstWithoutExceptions = + getContext().BuiltinInfo.isConstWithoutExceptions(BuiltinID); + if (FD->hasAttr() || + ((ConstWithoutErrnoAndExceptions || ConstWithoutExceptions) && + (!ConstWithoutErrnoAndExceptions || (!getLangOpts().MathErrno)))) { + switch (BuiltinIDIfNoAsmLabel) { + case Builtin::BIceil: + case Builtin::BIceilf: + case Builtin::BIceill: + case Builtin::BI__builtin_ceil: + case Builtin::BI__builtin_ceilf: + case Builtin::BI__builtin_ceilf16: + case Builtin::BI__builtin_ceill: + case Builtin::BI__builtin_ceilf128: + llvm_unreachable("NYI"); + + case Builtin::BIcopysign: + case Builtin::BIcopysignf: + case Builtin::BIcopysignl: + case Builtin::BI__builtin_copysign: + case Builtin::BI__builtin_copysignf: + case Builtin::BI__builtin_copysignf16: + case Builtin::BI__builtin_copysignl: + case Builtin::BI__builtin_copysignf128: + llvm_unreachable("NYI"); + + case Builtin::BIcos: + case Builtin::BIcosf: + case Builtin::BIcosl: + case Builtin::BI__builtin_cos: + case Builtin::BI__builtin_cosf: + case Builtin::BI__builtin_cosf16: + case Builtin::BI__builtin_cosl: + case Builtin::BI__builtin_cosf128: + llvm_unreachable("NYI"); + + case Builtin::BIexp: + case Builtin::BIexpf: + case Builtin::BIexpl: + case Builtin::BI__builtin_exp: + case Builtin::BI__builtin_expf: + case Builtin::BI__builtin_expf16: + case Builtin::BI__builtin_expl: + case Builtin::BI__builtin_expf128: + llvm_unreachable("NYI"); + + case Builtin::BIexp2: + case Builtin::BIexp2f: + case Builtin::BIexp2l: + case Builtin::BI__builtin_exp2: + case Builtin::BI__builtin_exp2f: + case Builtin::BI__builtin_exp2f16: + case Builtin::BI__builtin_exp2l: + case Builtin::BI__builtin_exp2f128: + llvm_unreachable("NYI"); + + case Builtin::BIfabs: + case Builtin::BIfabsf: + case Builtin::BIfabsl: + case Builtin::BI__builtin_fabs: + case Builtin::BI__builtin_fabsf: + case Builtin::BI__builtin_fabsf16: + case Builtin::BI__builtin_fabsl: + case Builtin::BI__builtin_fabsf128: { + mlir::Value Src0 = buildScalarExpr(E->getArg(0)); + auto SrcType = Src0.getType(); + auto Call = + builder.create(Src0.getLoc(), SrcType, Src0); + return RValue::get(Call->getResult(0)); + } + + case Builtin::BIfloor: + case Builtin::BIfloorf: + case Builtin::BIfloorl: + case Builtin::BI__builtin_floor: + case Builtin::BI__builtin_floorf: + case Builtin::BI__builtin_floorf16: + case Builtin::BI__builtin_floorl: + case Builtin::BI__builtin_floorf128: + llvm_unreachable("NYI"); + + case Builtin::BIfma: + case Builtin::BIfmaf: + case Builtin::BIfmal: + case Builtin::BI__builtin_fma: + case Builtin::BI__builtin_fmaf: + case Builtin::BI__builtin_fmaf16: + case Builtin::BI__builtin_fmal: + case Builtin::BI__builtin_fmaf128: + llvm_unreachable("NYI"); + + case Builtin::BIfmax: + case Builtin::BIfmaxf: + case Builtin::BIfmaxl: + case Builtin::BI__builtin_fmax: + case Builtin::BI__builtin_fmaxf: + case Builtin::BI__builtin_fmaxf16: + case Builtin::BI__builtin_fmaxl: + case Builtin::BI__builtin_fmaxf128: + llvm_unreachable("NYI"); + + case Builtin::BIfmin: + case Builtin::BIfminf: + case Builtin::BIfminl: + case Builtin::BI__builtin_fmin: + case Builtin::BI__builtin_fminf: + case Builtin::BI__builtin_fminf16: + case Builtin::BI__builtin_fminl: + case Builtin::BI__builtin_fminf128: + llvm_unreachable("NYI"); + + // fmod() is a special-case. It maps to the frem instruction rather than an + // LLVM intrinsic. + case Builtin::BIfmod: + case Builtin::BIfmodf: + case Builtin::BIfmodl: + case Builtin::BI__builtin_fmod: + case Builtin::BI__builtin_fmodf: + case Builtin::BI__builtin_fmodf16: + case Builtin::BI__builtin_fmodl: + case Builtin::BI__builtin_fmodf128: { + llvm_unreachable("NYI"); + } + + case Builtin::BIlog: + case Builtin::BIlogf: + case Builtin::BIlogl: + case Builtin::BI__builtin_log: + case Builtin::BI__builtin_logf: + case Builtin::BI__builtin_logf16: + case Builtin::BI__builtin_logl: + case Builtin::BI__builtin_logf128: + llvm_unreachable("NYI"); + + case Builtin::BIlog10: + case Builtin::BIlog10f: + case Builtin::BIlog10l: + case Builtin::BI__builtin_log10: + case Builtin::BI__builtin_log10f: + case Builtin::BI__builtin_log10f16: + case Builtin::BI__builtin_log10l: + case Builtin::BI__builtin_log10f128: + llvm_unreachable("NYI"); + + case Builtin::BIlog2: + case Builtin::BIlog2f: + case Builtin::BIlog2l: + case Builtin::BI__builtin_log2: + case Builtin::BI__builtin_log2f: + case Builtin::BI__builtin_log2f16: + case Builtin::BI__builtin_log2l: + case Builtin::BI__builtin_log2f128: + llvm_unreachable("NYI"); + + case Builtin::BInearbyint: + case Builtin::BInearbyintf: + case Builtin::BInearbyintl: + case Builtin::BI__builtin_nearbyint: + case Builtin::BI__builtin_nearbyintf: + case Builtin::BI__builtin_nearbyintl: + case Builtin::BI__builtin_nearbyintf128: + llvm_unreachable("NYI"); + + case Builtin::BIpow: + case Builtin::BIpowf: + case Builtin::BIpowl: + case Builtin::BI__builtin_pow: + case Builtin::BI__builtin_powf: + case Builtin::BI__builtin_powf16: + case Builtin::BI__builtin_powl: + case Builtin::BI__builtin_powf128: + llvm_unreachable("NYI"); + + case Builtin::BIrint: + case Builtin::BIrintf: + case Builtin::BIrintl: + case Builtin::BI__builtin_rint: + case Builtin::BI__builtin_rintf: + case Builtin::BI__builtin_rintf16: + case Builtin::BI__builtin_rintl: + case Builtin::BI__builtin_rintf128: + llvm_unreachable("NYI"); + + case Builtin::BIround: + case Builtin::BIroundf: + case Builtin::BIroundl: + case Builtin::BI__builtin_round: + case Builtin::BI__builtin_roundf: + case Builtin::BI__builtin_roundf16: + case Builtin::BI__builtin_roundl: + case Builtin::BI__builtin_roundf128: + llvm_unreachable("NYI"); + + case Builtin::BIsin: + case Builtin::BIsinf: + case Builtin::BIsinl: + case Builtin::BI__builtin_sin: + case Builtin::BI__builtin_sinf: + case Builtin::BI__builtin_sinf16: + case Builtin::BI__builtin_sinl: + case Builtin::BI__builtin_sinf128: + llvm_unreachable("NYI"); + + case Builtin::BIsqrt: + case Builtin::BIsqrtf: + case Builtin::BIsqrtl: + case Builtin::BI__builtin_sqrt: + case Builtin::BI__builtin_sqrtf: + case Builtin::BI__builtin_sqrtf16: + case Builtin::BI__builtin_sqrtl: + case Builtin::BI__builtin_sqrtf128: + llvm_unreachable("NYI"); + + case Builtin::BItrunc: + case Builtin::BItruncf: + case Builtin::BItruncl: + case Builtin::BI__builtin_trunc: + case Builtin::BI__builtin_truncf: + case Builtin::BI__builtin_truncf16: + case Builtin::BI__builtin_truncl: + case Builtin::BI__builtin_truncf128: + llvm_unreachable("NYI"); + + case Builtin::BIlround: + case Builtin::BIlroundf: + case Builtin::BIlroundl: + case Builtin::BI__builtin_lround: + case Builtin::BI__builtin_lroundf: + case Builtin::BI__builtin_lroundl: + case Builtin::BI__builtin_lroundf128: + llvm_unreachable("NYI"); + + case Builtin::BIllround: + case Builtin::BIllroundf: + case Builtin::BIllroundl: + case Builtin::BI__builtin_llround: + case Builtin::BI__builtin_llroundf: + case Builtin::BI__builtin_llroundl: + case Builtin::BI__builtin_llroundf128: + llvm_unreachable("NYI"); + + case Builtin::BIlrint: + case Builtin::BIlrintf: + case Builtin::BIlrintl: + case Builtin::BI__builtin_lrint: + case Builtin::BI__builtin_lrintf: + case Builtin::BI__builtin_lrintl: + case Builtin::BI__builtin_lrintf128: + llvm_unreachable("NYI"); + + case Builtin::BIllrint: + case Builtin::BIllrintf: + case Builtin::BIllrintl: + case Builtin::BI__builtin_llrint: + case Builtin::BI__builtin_llrintf: + case Builtin::BI__builtin_llrintl: + case Builtin::BI__builtin_llrintf128: + llvm_unreachable("NYI"); + + default: + break; + } + } + + switch (BuiltinIDIfNoAsmLabel) { + default: + break; + + case Builtin::BIprintf: + if (getTarget().getTriple().isNVPTX() || + getTarget().getTriple().isAMDGCN()) { + llvm_unreachable("NYI"); + } + break; + + // C stdarg builtins. + case Builtin::BI__builtin_stdarg_start: + case Builtin::BI__builtin_va_start: + case Builtin::BI__va_start: + case Builtin::BI__builtin_va_end: { + buildVAStartEnd(BuiltinID == Builtin::BI__va_start + ? buildScalarExpr(E->getArg(0)) + : buildVAListRef(E->getArg(0)).getPointer(), + BuiltinID != Builtin::BI__builtin_va_end); + return {}; + } + case Builtin::BI__builtin_va_copy: { + auto dstPtr = buildVAListRef(E->getArg(0)).getPointer(); + auto srcPtr = buildVAListRef(E->getArg(1)).getPointer(); + builder.create(dstPtr.getLoc(), dstPtr, srcPtr); + return {}; + } + + // C++ std:: builtins. + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + case Builtin::BIforward: + case Builtin::BIas_const: + return RValue::get(buildLValue(E->getArg(0)).getPointer()); + case Builtin::BI__GetExceptionInfo: { + llvm_unreachable("NYI"); + } + + case Builtin::BI__fastfail: + llvm_unreachable("NYI"); + + case Builtin::BI__builtin_coro_id: + case Builtin::BI__builtin_coro_promise: + case Builtin::BI__builtin_coro_resume: + case Builtin::BI__builtin_coro_noop: + case Builtin::BI__builtin_coro_destroy: + case Builtin::BI__builtin_coro_done: + case Builtin::BI__builtin_coro_alloc: + case Builtin::BI__builtin_coro_begin: + case Builtin::BI__builtin_coro_end: + case Builtin::BI__builtin_coro_suspend: + case Builtin::BI__builtin_coro_align: + llvm_unreachable("NYI"); + + case Builtin::BI__builtin_coro_frame: { + return buildCoroutineFrame(); + } + case Builtin::BI__builtin_coro_free: + case Builtin::BI__builtin_coro_size: { + GlobalDecl gd{FD}; + mlir::Type ty = CGM.getTypes().GetFunctionType( + CGM.getTypes().arrangeGlobalDeclaration(GD)); + const auto *ND = cast(GD.getDecl()); + auto fnOp = + CGM.GetOrCreateCIRFunction(ND->getName(), ty, gd, /*ForVTable=*/false, + /*DontDefer=*/false); + fnOp.setBuiltinAttr(mlir::UnitAttr::get(builder.getContext())); + return buildCall(E->getCallee()->getType(), CIRGenCallee::forDirect(fnOp), + E, ReturnValue); + } + case Builtin::BI__builtin_dynamic_object_size: { + // Fallthrough below, assert until we have a testcase. + llvm_unreachable("NYI"); + } + case Builtin::BI__builtin_object_size: { + unsigned Type = + E->getArg(1)->EvaluateKnownConstInt(getContext()).getZExtValue(); + auto ResType = ConvertType(E->getType()).dyn_cast(); + assert(ResType && "not sure what to do?"); + + // We pass this builtin onto the optimizer so that it can figure out the + // object size in more complex cases. + bool IsDynamic = BuiltinID == Builtin::BI__builtin_dynamic_object_size; + return RValue::get(emitBuiltinObjectSize(E->getArg(0), Type, ResType, + /*EmittedE=*/nullptr, IsDynamic)); + } + case Builtin::BImemcpy: + case Builtin::BI__builtin_memcpy: + case Builtin::BImempcpy: + case Builtin::BI__builtin_mempcpy: + Address Dest = buildPointerWithAlignment(E->getArg(0)); + Address Src = buildPointerWithAlignment(E->getArg(1)); + mlir::Value SizeVal = buildScalarExpr(E->getArg(2)); + buildNonNullArgCheck(RValue::get(Dest.getPointer()), + E->getArg(0)->getType(), E->getArg(0)->getExprLoc(), + FD, 0); + buildNonNullArgCheck(RValue::get(Src.getPointer()), E->getArg(1)->getType(), + E->getArg(1)->getExprLoc(), FD, 1); + builder.createMemCpy(getLoc(E->getSourceRange()), Dest.getPointer(), + Src.getPointer(), SizeVal); + if (BuiltinID == Builtin::BImempcpy || + BuiltinID == Builtin::BI__builtin_mempcpy) + llvm_unreachable("mempcpy is NYI"); + else + return RValue::get(Dest.getPointer()); + } + + // If this is an alias for a lib function (e.g. __builtin_sin), emit + // the call using the normal call path, but using the unmangled + // version of the function name. + if (getContext().BuiltinInfo.isLibFunction(BuiltinID)) + return buildLibraryCall(*this, FD, E, + CGM.getBuiltinLibFunction(FD, BuiltinID)); + + // If this is a predefined lib function (e.g. malloc), emit the call + // using exactly the normal call path. + if (getContext().BuiltinInfo.isPredefinedLibFunction(BuiltinID)) + return buildLibraryCall(*this, FD, E, + buildScalarExpr(E->getCallee()).getDefiningOp()); + + // Check that a call to a target specific builtin has the correct target + // features. + // This is down here to avoid non-target specific builtins, however, if + // generic builtins start to require generic target features then we + // can move this up to the beginning of the function. + // checkTargetFeatures(E, FD); + + if (unsigned VectorWidth = + getContext().BuiltinInfo.getRequiredVectorWidth(BuiltinID)) + llvm_unreachable("NYI"); + + // See if we have a target specific intrinsic. + auto Name = getContext().BuiltinInfo.getName(BuiltinID).str(); + Intrinsic::ID IntrinsicID = Intrinsic::not_intrinsic; + StringRef Prefix = + llvm::Triple::getArchTypePrefix(getTarget().getTriple().getArch()); + if (!Prefix.empty()) { + IntrinsicID = Intrinsic::getIntrinsicForClangBuiltin(Prefix.data(), Name); + // NOTE we don't need to perform a compatibility flag check here since the + // intrinsics are declared in Builtins*.def via LANGBUILTIN which filter the + // MS builtins via ALL_MS_LANGUAGES and are filtered earlier. + if (IntrinsicID == Intrinsic::not_intrinsic) + IntrinsicID = Intrinsic::getIntrinsicForMSBuiltin(Prefix.data(), Name); + } + + if (IntrinsicID != Intrinsic::not_intrinsic) { + llvm_unreachable("NYI"); + } + + // Some target-specific builtins can have aggregate return values, e.g. + // __builtin_arm_mve_vld2q_u32. So if the result is an aggregate, force + // ReturnValue to be non-null, so that the target-specific emission code can + // always just emit into it. + TypeEvaluationKind EvalKind = getEvaluationKind(E->getType()); + if (EvalKind == TEK_Aggregate && ReturnValue.isNull()) { + llvm_unreachable("NYI"); + } + + // Now see if we can emit a target-specific builtin. + if (auto v = buildTargetBuiltinExpr(BuiltinID, E, ReturnValue)) { + llvm_unreachable("NYI"); + } + + llvm_unreachable("NYI"); + // ErrorUnsupported(E, "builtin function"); + + // Unknown builtin, for now just dump it out and return undef. + return GetUndefRValue(E->getType()); +} + +static mlir::Value buildTargetArchBuiltinExpr(CIRGenFunction *CGF, + unsigned BuiltinID, + const CallExpr *E, + ReturnValueSlot ReturnValue, + llvm::Triple::ArchType Arch) { + llvm_unreachable("NYI"); + return {}; +} + +mlir::Value +CIRGenFunction::buildTargetBuiltinExpr(unsigned BuiltinID, const CallExpr *E, + ReturnValueSlot ReturnValue) { + if (getContext().BuiltinInfo.isAuxBuiltinID(BuiltinID)) { + assert(getContext().getAuxTargetInfo() && "Missing aux target info"); + return buildTargetArchBuiltinExpr( + this, getContext().BuiltinInfo.getAuxBuiltinID(BuiltinID), E, + ReturnValue, getContext().getAuxTargetInfo()->getTriple().getArch()); + } + + return buildTargetArchBuiltinExpr(this, BuiltinID, E, ReturnValue, + getTarget().getTriple().getArch()); +} + +void CIRGenFunction::buildVAStartEnd(mlir::Value ArgValue, bool IsStart) { + // LLVM codegen casts to *i8, no real gain on doing this for CIRGen this + // early, defer to LLVM lowering. + if (IsStart) + builder.create(ArgValue.getLoc(), ArgValue); + else + builder.create(ArgValue.getLoc(), ArgValue); +} + +/// Checks if using the result of __builtin_object_size(p, @p From) in place of +/// __builtin_object_size(p, @p To) is correct +static bool areBOSTypesCompatible(int From, int To) { + // Note: Our __builtin_object_size implementation currently treats Type=0 and + // Type=2 identically. Encoding this implementation detail here may make + // improving __builtin_object_size difficult in the future, so it's omitted. + return From == To || (From == 0 && To == 1) || (From == 3 && To == 2); +} + +/// Returns a Value corresponding to the size of the given expression. +/// This Value may be either of the following: +/// +/// - Reference an argument if `pass_object_size` is used. +/// - A call to a `cir.objsize`. +/// +/// EmittedE is the result of emitting `E` as a scalar expr. If it's non-null +/// and we wouldn't otherwise try to reference a pass_object_size parameter, +/// we'll call `cir.objsize` on EmittedE, rather than emitting E. +mlir::Value CIRGenFunction::emitBuiltinObjectSize(const Expr *E, unsigned Type, + mlir::cir::IntType ResType, + mlir::Value EmittedE, + bool IsDynamic) { + // We need to reference an argument if the pointer is a parameter with the + // pass_object_size attribute. + if (auto *D = dyn_cast(E->IgnoreParenImpCasts())) { + auto *Param = dyn_cast(D->getDecl()); + auto *PS = D->getDecl()->getAttr(); + if (Param != nullptr && PS != nullptr && + areBOSTypesCompatible(PS->getType(), Type)) { + auto Iter = SizeArguments.find(Param); + assert(Iter != SizeArguments.end()); + + const ImplicitParamDecl *D = Iter->second; + auto DIter = LocalDeclMap.find(D); + assert(DIter != LocalDeclMap.end()); + + return buildLoadOfScalar(DIter->second, /*Volatile=*/false, + getContext().getSizeType(), E->getBeginLoc()); + } + } + + // LLVM can't handle Type=3 appropriately, and __builtin_object_size shouldn't + // evaluate E for side-effects. In either case, just like original LLVM + // lowering, we shouldn't lower to `cir.objsize`. + if (Type == 3 || (!EmittedE && E->HasSideEffects(getContext()))) + llvm_unreachable("NYI"); + + auto Ptr = EmittedE ? EmittedE : buildScalarExpr(E); + assert(Ptr.getType().isa() && + "Non-pointer passed to __builtin_object_size?"); + + // LLVM intrinsics (which CIR lowers to at some point, only supports 0 + // and 2, account for that right now. + mlir::cir::SizeInfoType sizeInfoTy = ((Type & 2) != 0) + ? mlir::cir::SizeInfoType::min + : mlir::cir::SizeInfoType::max; + // TODO(cir): Heads up for LLVM lowering, For GCC compatibility, + // __builtin_object_size treat NULL as unknown size. + return builder.create( + getLoc(E->getSourceRange()), ResType, Ptr, sizeInfoTy, IsDynamic); +} + +mlir::Value CIRGenFunction::evaluateOrEmitBuiltinObjectSize( + const Expr *E, unsigned Type, mlir::cir::IntType ResType, + mlir::Value EmittedE, bool IsDynamic) { + uint64_t ObjectSize; + if (!E->tryEvaluateObjectSize(ObjectSize, getContext(), Type)) + return emitBuiltinObjectSize(E, Type, ResType, EmittedE, IsDynamic); + return builder.getConstInt(getLoc(E->getSourceRange()), ResType, ObjectSize); +} + +/// Given a builtin id for a function like "__builtin_fabsf", return a Function* +/// for "fabsf". +mlir::cir::FuncOp CIRGenModule::getBuiltinLibFunction(const FunctionDecl *FD, + unsigned BuiltinID) { + assert(astCtx.BuiltinInfo.isLibFunction(BuiltinID)); + + // Get the name, skip over the __builtin_ prefix (if necessary). + StringRef Name; + GlobalDecl D(FD); + + // TODO: This list should be expanded or refactored after all GCC-compatible + // std libcall builtins are implemented. + static SmallDenseMap F128Builtins{ + {Builtin::BI__builtin___fprintf_chk, "__fprintf_chkieee128"}, + {Builtin::BI__builtin___printf_chk, "__printf_chkieee128"}, + {Builtin::BI__builtin___snprintf_chk, "__snprintf_chkieee128"}, + {Builtin::BI__builtin___sprintf_chk, "__sprintf_chkieee128"}, + {Builtin::BI__builtin___vfprintf_chk, "__vfprintf_chkieee128"}, + {Builtin::BI__builtin___vprintf_chk, "__vprintf_chkieee128"}, + {Builtin::BI__builtin___vsnprintf_chk, "__vsnprintf_chkieee128"}, + {Builtin::BI__builtin___vsprintf_chk, "__vsprintf_chkieee128"}, + {Builtin::BI__builtin_fprintf, "__fprintfieee128"}, + {Builtin::BI__builtin_printf, "__printfieee128"}, + {Builtin::BI__builtin_snprintf, "__snprintfieee128"}, + {Builtin::BI__builtin_sprintf, "__sprintfieee128"}, + {Builtin::BI__builtin_vfprintf, "__vfprintfieee128"}, + {Builtin::BI__builtin_vprintf, "__vprintfieee128"}, + {Builtin::BI__builtin_vsnprintf, "__vsnprintfieee128"}, + {Builtin::BI__builtin_vsprintf, "__vsprintfieee128"}, + {Builtin::BI__builtin_fscanf, "__fscanfieee128"}, + {Builtin::BI__builtin_scanf, "__scanfieee128"}, + {Builtin::BI__builtin_sscanf, "__sscanfieee128"}, + {Builtin::BI__builtin_vfscanf, "__vfscanfieee128"}, + {Builtin::BI__builtin_vscanf, "__vscanfieee128"}, + {Builtin::BI__builtin_vsscanf, "__vsscanfieee128"}, + {Builtin::BI__builtin_nexttowardf128, "__nexttowardieee128"}, + }; + + // The AIX library functions frexpl, ldexpl, and modfl are for 128-bit + // IBM 'long double' (i.e. __ibm128). Map to the 'double' versions + // if it is 64-bit 'long double' mode. + static SmallDenseMap AIXLongDouble64Builtins{ + {Builtin::BI__builtin_frexpl, "frexp"}, + {Builtin::BI__builtin_ldexpl, "ldexp"}, + {Builtin::BI__builtin_modfl, "modf"}, + }; + + // If the builtin has been declared explicitly with an assembler label, + // use the mangled name. This differs from the plain label on platforms + // that prefix labels. + if (FD->hasAttr()) + Name = getMangledName(D); + else { + // TODO: This mutation should also be applied to other targets other than + // PPC, after backend supports IEEE 128-bit style libcalls. + if (getTriple().isPPC64() && + &getTarget().getLongDoubleFormat() == &llvm::APFloat::IEEEquad() && + F128Builtins.find(BuiltinID) != F128Builtins.end()) + Name = F128Builtins[BuiltinID]; + else if (getTriple().isOSAIX() && + &getTarget().getLongDoubleFormat() == + &llvm::APFloat::IEEEdouble() && + AIXLongDouble64Builtins.find(BuiltinID) != + AIXLongDouble64Builtins.end()) + Name = AIXLongDouble64Builtins[BuiltinID]; + else + Name = astCtx.BuiltinInfo.getName(BuiltinID).substr(10); + } + + auto Ty = getTypes().ConvertType(FD->getType()); + return GetOrCreateCIRFunction(Name, Ty, D, /*ForVTable=*/false); +} \ No newline at end of file diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp new file mode 100644 index 000000000000..8d88746d017e --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -0,0 +1,138 @@ +//===--- CGCXX.cpp - Emit LLVM Code for declarations ----------------------===// +// +// 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 dealing with C++ code generation. +// +//===----------------------------------------------------------------------===// + +// We might split this into multiple files if it gets too unwieldy + +#include "CIRGenCXXABI.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" + +#include "clang/AST/GlobalDecl.h" +#include "llvm/Support/ErrorHandling.h" +#include + +using namespace clang; +using namespace cir; + +static void buildDeclInit(CIRGenFunction &CGF, const VarDecl *D, + Address DeclPtr) { + assert((D->hasGlobalStorage() || + (D->hasLocalStorage() && + CGF.getContext().getLangOpts().OpenCLCPlusPlus)) && + "VarDecl must have global or local (in the case of OpenCL) storage!"); + assert(!D->getType()->isReferenceType() && + "Should not call buildDeclInit on a reference!"); + + QualType type = D->getType(); + LValue lv = CGF.makeAddrLValue(DeclPtr, type); + + const Expr *Init = D->getInit(); + switch (CIRGenFunction::getEvaluationKind(type)) { + case TEK_Aggregate: + CGF.buildAggExpr( + Init, AggValueSlot::forLValue(lv, AggValueSlot::IsDestructed, + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased, + AggValueSlot::DoesNotOverlap)); + return; + case TEK_Scalar: + CGF.buildScalarInit(Init, CGF.getLoc(D->getLocation()), lv, false); + return; + case TEK_Complex: + llvm_unreachable("complext evaluation NYI"); + } +} + +static void buildDeclDestory(CIRGenFunction &CGF, const VarDecl *D, + Address DeclPtr) { + // Honor __attribute__((no_destroy)) and bail instead of attempting + // to emit a reference to a possibly nonexistent destructor, which + // in turn can cause a crash. This will result in a global constructor + // that isn't balanced out by a destructor call as intended by the + // attribute. This also checks for -fno-c++-static-destructors and + // bails even if the attribute is not present. + assert(D->needsDestruction(CGF.getContext()) == QualType::DK_cxx_destructor); + + auto &CGM = CGF.CGM; + + // If __cxa_atexit is disabled via a flag, a different helper function is + // generated elsewhere which uses atexit instead, and it takes the destructor + // directly. + auto UsingExternalHelper = CGM.getCodeGenOpts().CXAAtExit; + QualType type = D->getType(); + const CXXRecordDecl *Record = type->getAsCXXRecordDecl(); + bool CanRegisterDestructor = + Record && (!CGM.getCXXABI().HasThisReturn( + GlobalDecl(Record->getDestructor(), Dtor_Complete)) || + CGM.getCXXABI().canCallMismatchedFunctionType()); + if (Record && (CanRegisterDestructor || UsingExternalHelper)) { + assert(!D->getTLSKind() && "TLS NYI"); + CXXDestructorDecl *Dtor = Record->getDestructor(); + CGM.getCXXABI().buildDestructorCall(CGF, Dtor, Dtor_Complete, + /*ForVirtualBase=*/false, + /*Delegating=*/false, DeclPtr, type); + } else { + llvm_unreachable("array destructors not yet supported!"); + } +} + +mlir::cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl GD) { + const auto &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD); + auto Fn = getAddrOfCXXStructor(GD, &FnInfo, /*FnType=*/nullptr, + /*DontDefer=*/true, ForDefinition); + + setFunctionLinkage(GD, Fn); + CIRGenFunction CGF{*this, builder}; + CurCGF = &CGF; + { + mlir::OpBuilder::InsertionGuard guard(builder); + CGF.generateCode(GD, Fn, FnInfo); + } + CurCGF = nullptr; + + // TODO: setNonAliasAttributes + // TODO: SetLLVMFunctionAttributesForDefinition + return Fn; +} + +void CIRGenModule::codegenGlobalInitCxxStructor(const VarDecl *D, + mlir::cir::GlobalOp Addr, + bool NeedsCtor, + bool NeedsDtor) { + assert(D && " Expected a global declaration!"); + CIRGenFunction CGF{*this, builder, true}; + CurCGF = &CGF; + CurCGF->CurFn = Addr; + Addr.setAstAttr(mlir::cir::ASTVarDeclAttr::get(builder.getContext(), D)); + + if (NeedsCtor) { + mlir::OpBuilder::InsertionGuard guard(builder); + auto block = builder.createBlock(&Addr.getCtorRegion()); + builder.setInsertionPointToStart(block); + Address DeclAddr(getAddrOfGlobalVar(D), getASTContext().getDeclAlign(D)); + buildDeclInit(CGF, D, DeclAddr); + builder.setInsertionPointToEnd(block); + builder.create(Addr->getLoc()); + } + + if (NeedsDtor) { + mlir::OpBuilder::InsertionGuard guard(builder); + auto block = builder.createBlock(&Addr.getDtorRegion()); + builder.setInsertionPointToStart(block); + Address DeclAddr(getAddrOfGlobalVar(D), getASTContext().getDeclAlign(D)); + buildDeclDestory(CGF, D, DeclAddr); + builder.setInsertionPointToEnd(block); + builder.create(Addr->getLoc()); + } + + CurCGF = nullptr; +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp new file mode 100644 index 000000000000..6a22a372c5d0 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp @@ -0,0 +1,74 @@ +//===----- CirGenCXXABI.cpp - Interface to C++ ABIs -----------------------===// +// +// 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 provides an abstract class for C++ code generation. Concrete subclasses +// of this implement code generation for specific C++ ABIs. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenCXXABI.h" + +#include "clang/AST/Decl.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" +#include "clang/AST/RecordLayout.h" + +using namespace cir; +using namespace clang; + +CIRGenCXXABI::~CIRGenCXXABI() {} + +CIRGenCXXABI::AddedStructorArgCounts CIRGenCXXABI::addImplicitConstructorArgs( + CIRGenFunction &CGF, const clang::CXXConstructorDecl *D, + clang::CXXCtorType Type, bool ForVirtualBase, bool Delegating, + CallArgList &Args) { + auto AddedArgs = + getImplicitConstructorArgs(CGF, D, Type, ForVirtualBase, Delegating); + for (size_t i = 0; i < AddedArgs.Prefix.size(); ++i) + Args.insert(Args.begin() + 1 + i, + CallArg(RValue::get(AddedArgs.Prefix[i].Value), + AddedArgs.Prefix[i].Type)); + for (const auto &arg : AddedArgs.Suffix) + Args.add(RValue::get(arg.Value), arg.Type); + return AddedStructorArgCounts(AddedArgs.Prefix.size(), + AddedArgs.Suffix.size()); +} + +bool CIRGenCXXABI::NeedsVTTParameter(GlobalDecl GD) { return false; } + +void CIRGenCXXABI::buildThisParam(CIRGenFunction &CGF, + FunctionArgList ¶ms) { + const auto *MD = cast(CGF.CurGD.getDecl()); + + // FIXME: I'm not entirely sure I like using a fake decl just for code + // generation. Maybe we can come up with a better way? + auto *ThisDecl = + ImplicitParamDecl::Create(CGM.getASTContext(), nullptr, MD->getLocation(), + &CGM.getASTContext().Idents.get("this"), + MD->getThisType(), ImplicitParamDecl::CXXThis); + params.push_back(ThisDecl); + CGF.CXXABIThisDecl = ThisDecl; + + // Compute the presumed alignment of 'this', which basically comes down to + // whether we know it's a complete object or not. + auto &Layout = CGF.getContext().getASTRecordLayout(MD->getParent()); + if (MD->getParent()->getNumVBases() == 0 || + MD->getParent()->isEffectivelyFinal() || + isThisCompleteObject(CGF.CurGD)) { + CGF.CXXABIThisAlignment = Layout.getAlignment(); + } else { + llvm_unreachable("NYI"); + } +} + +mlir::cir::GlobalLinkageKind CIRGenCXXABI::getCXXDestructorLinkage( + GVALinkage Linkage, const CXXDestructorDecl *Dtor, CXXDtorType DT) const { + // Delegate back to CGM by default. + return CGM.getCIRLinkageForDeclarator(Dtor, Linkage, + /*IsConstantVariable=*/false); +} \ No newline at end of file diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h new file mode 100644 index 000000000000..e1fa33a3f22f --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -0,0 +1,307 @@ +//===----- CIRGenCXXABI.h - Interface to C++ ABIs ---------------*- C++ -*-===// +// +// 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 provides an abstract class for C++ code generation. Concrete subclasses +// of this implement code generation for specific C++ ABIs. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H +#define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H + +#include "CIRGenCall.h" +#include "CIRGenCleanup.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" + +#include "clang/AST/Mangle.h" + +namespace cir { + +class CIRGenFunction; +class CIRGenFunctionInfo; + +/// Implements C++ ABI-specific code generation functions. +class CIRGenCXXABI { +protected: + cir::CIRGenModule &CGM; + std::unique_ptr MangleCtx; + + CIRGenCXXABI(CIRGenModule &CGM) + : CGM{CGM}, MangleCtx(CGM.getASTContext().createMangleContext()) {} + + clang::ASTContext &getContext() const { return CGM.getASTContext(); } + +public: + /// Similar to AddedStructorArgs, but only notes the number of additional + /// arguments. + struct AddedStructorArgCounts { + unsigned Prefix = 0; + unsigned Suffix = 0; + AddedStructorArgCounts() = default; + AddedStructorArgCounts(unsigned P, unsigned S) : Prefix(P), Suffix(S) {} + static AddedStructorArgCounts prefix(unsigned N) { return {N, 0}; } + static AddedStructorArgCounts suffix(unsigned N) { return {0, N}; } + }; + + /// Additional implicit arguments to add to the beginning (Prefix) and end + /// (Suffix) of a constructor / destructor arg list. + /// + /// Note that Prefix should actually be inserted *after* the first existing + /// arg; `this` arguments always come first. + struct AddedStructorArgs { + struct Arg { + mlir::Value Value; + clang::QualType Type; + }; + llvm::SmallVector Prefix; + llvm::SmallVector Suffix; + AddedStructorArgs() = default; + AddedStructorArgs(llvm::SmallVector P, llvm::SmallVector S) + : Prefix(std::move(P)), Suffix(std::move(S)) {} + static AddedStructorArgs prefix(llvm::SmallVector Args) { + return {std::move(Args), {}}; + } + static AddedStructorArgs suffix(llvm::SmallVector Args) { + return {{}, std::move(Args)}; + } + }; + + /// Build the signature of the given constructor or destructor vairant by + /// adding any required parameters. For convenience, ArgTys has been + /// initialized with the type of 'this'. + virtual AddedStructorArgCounts + buildStructorSignature(clang::GlobalDecl GD, + llvm::SmallVectorImpl &ArgTys) = 0; + + AddedStructorArgCounts + addImplicitConstructorArgs(CIRGenFunction &CGF, + const clang::CXXConstructorDecl *D, + clang::CXXCtorType Type, bool ForVirtualBase, + bool Delegating, CallArgList &Args); + + clang::ImplicitParamDecl *getThisDecl(CIRGenFunction &CGF) { + return CGF.CXXABIThisDecl; + } + + virtual AddedStructorArgs getImplicitConstructorArgs( + CIRGenFunction &CGF, const clang::CXXConstructorDecl *D, + clang::CXXCtorType Type, bool ForVirtualBase, bool Delegating) = 0; + + /// Emit the ABI-specific prolog for the function + virtual void buildInstanceFunctionProlog(CIRGenFunction &CGF) = 0; + + /// Get the type of the implicit "this" parameter used by a method. May return + /// zero if no specific type is applicable, e.g. if the ABI expects the "this" + /// parameter to point to some artificial offset in a complete object due to + /// vbases being reordered. + virtual const clang::CXXRecordDecl * + getThisArgumentTypeForMethod(const clang::CXXMethodDecl *MD) { + return MD->getParent(); + } + + /// Return whether the given global decl needs a VTT parameter. + virtual bool NeedsVTTParameter(clang::GlobalDecl GD); + + /// If the C++ ABI requires the given type be returned in a particular way, + /// this method sets RetAI and returns true. + virtual bool classifyReturnType(CIRGenFunctionInfo &FI) const = 0; + + /// Gets the mangle context. + clang::MangleContext &getMangleContext() { return *MangleCtx; } + + clang::ImplicitParamDecl *&getStructorImplicitParamDecl(CIRGenFunction &CGF) { + return CGF.CXXStructorImplicitParamDecl; + } + + /// Perform ABI-specific "this" argument adjustment required prior to + /// a call of a virtual function. + /// The "VirtualCall" argument is true iff the call itself is virtual. + virtual Address adjustThisArgumentForVirtualFunctionCall(CIRGenFunction &CGF, + GlobalDecl GD, + Address This, + bool VirtualCall) { + return This; + } + + /// Build a parameter variable suitable for 'this'. + void buildThisParam(CIRGenFunction &CGF, FunctionArgList &Params); + + /// Loads the incoming C++ this pointer as it was passed by the caller. + mlir::Value loadIncomingCXXThis(CIRGenFunction &CGF); + + /// Determine whether there's something special about the rules of the ABI + /// tell us that 'this' is a complete object within the given function. + /// Obvious common logic like being defined on a final class will have been + /// taken care of by the caller. + virtual bool isThisCompleteObject(clang::GlobalDecl GD) const = 0; + + /// Get the implicit (second) parameter that comes after the "this" pointer, + /// or nullptr if there is isn't one. + virtual mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &CGF, + const CXXDestructorDecl *DD, + CXXDtorType Type, + bool ForVirtualBase, + bool Delegating) = 0; + + /// Emit constructor variants required by this ABI. + virtual void buildCXXConstructors(const clang::CXXConstructorDecl *D) = 0; + /// Emit dtor variants required by this ABI. + virtual void buildCXXDestructors(const clang::CXXDestructorDecl *D) = 0; + + /// Emit the destructor call. + virtual void buildDestructorCall(CIRGenFunction &CGF, + const CXXDestructorDecl *DD, + CXXDtorType Type, bool ForVirtualBase, + bool Delegating, Address This, + QualType ThisTy) = 0; + + virtual size_t getSrcArgforCopyCtor(const CXXConstructorDecl *, + FunctionArgList &Args) const = 0; + + /// Get the address of the vtable for the given record decl which should be + /// used for the vptr at the given offset in RD. + virtual mlir::cir::GlobalOp getAddrOfVTable(const CXXRecordDecl *RD, + CharUnits VPtrOffset) = 0; + + /// Build a virtual function pointer in the ABI-specific way. + virtual CIRGenCallee getVirtualFunctionPointer(CIRGenFunction &CGF, + GlobalDecl GD, Address This, + mlir::Type Ty, + SourceLocation Loc) = 0; + + /// Checks if ABI requires extra virtual offset for vtable field. + virtual bool + isVirtualOffsetNeededForVTableField(CIRGenFunction &CGF, + CIRGenFunction::VPtr Vptr) = 0; + + /// Determine whether it's possible to emit a vtable for \p RD, even + /// though we do not know that the vtable has been marked as used by semantic + /// analysis. + virtual bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const = 0; + + /// Emits the VTable definitions required for the given record type. + virtual void emitVTableDefinitions(CIRGenVTables &CGVT, + const CXXRecordDecl *RD) = 0; + + /// Emit any tables needed to implement virtual inheritance. For Itanium, + /// this emits virtual table tables. + virtual void emitVirtualInheritanceTables(const CXXRecordDecl *RD) = 0; + + virtual mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc, + QualType Ty) = 0; + virtual CatchTypeInfo + getAddrOfCXXCatchHandlerType(mlir::Location loc, QualType Ty, + QualType CatchHandlerType) = 0; + + /// Returns true if the given destructor type should be emitted as a linkonce + /// delegating thunk, regardless of whether the dtor is defined in this TU or + /// not. + virtual bool useThunkForDtorVariant(const CXXDestructorDecl *Dtor, + CXXDtorType DT) const = 0; + + virtual mlir::cir::GlobalLinkageKind + getCXXDestructorLinkage(GVALinkage Linkage, const CXXDestructorDecl *Dtor, + CXXDtorType DT) const; + + /// Get the address point of the vtable for the given base subobject. + virtual mlir::Value + getVTableAddressPoint(BaseSubobject Base, + const CXXRecordDecl *VTableClass) = 0; + + /// Get the address point of the vtable for the given base subobject while + /// building a constructor or a destructor. + virtual mlir::Value + getVTableAddressPointInStructor(CIRGenFunction &CGF, const CXXRecordDecl *RD, + BaseSubobject Base, + const CXXRecordDecl *NearestVBase) = 0; + + /// Specify how one should pass an argument of a record type. + enum class RecordArgABI { + /// Pass it using the normal C aggregate rules for the ABI, potentially + /// introducing extra copies and passing some or all of it in registers. + Default = 0, + + /// Pass it on the stack using its defined layout. The argument must be + /// evaluated directly into the correct stack position in the arguments + /// area, and the call machinery must not move it or introduce extra copies. + DirectInMemory, + + /// Pass it as a pointer to temporary memory. + Indirect + }; + + /// Returns how an argument of the given record type should be passed. + virtual RecordArgABI + getRecordArgABI(const clang::CXXRecordDecl *RD) const = 0; + + /// Insert any ABI-specific implicit parameters into the parameter list for a + /// function. This generally involves extra data for constructors and + /// destructors. + /// + /// ABIs may also choose to override the return type, which has been + /// initialized with the type of 'this' if HasThisReturn(CGF.CurGD) is true or + /// the formal return type of the function otherwise. + virtual void addImplicitStructorParams(CIRGenFunction &CGF, + clang::QualType &ResTy, + FunctionArgList &Params) = 0; + + /// Checks if ABI requires to initialize vptrs for given dynamic class. + virtual bool + doStructorsInitializeVPtrs(const clang::CXXRecordDecl *VTableClass) = 0; + + /// Returns true if the given constructor or destructor is one of the kinds + /// that the ABI says returns 'this' (only applies when called non-virtually + /// for destructors). + /// + /// There currently is no way to indicate if a destructor returns 'this' when + /// called virtually, and CIR generation does not support this case. + virtual bool HasThisReturn(clang::GlobalDecl GD) const { return false; } + + virtual bool hasMostDerivedReturn(clang::GlobalDecl GD) const { + return false; + } + + /// Returns true if the target allows calling a function through a pointer + /// with a different signature than the actual function (or equivalently, + /// bitcasting a function or function pointer to a different function type). + /// In principle in the most general case this could depend on the target, the + /// calling convention, and the actual types of the arguments and return + /// value. Here it just means whether the signature mismatch could *ever* be + /// allowed; in other words, does the target do strict checking of signatures + /// for all calls. + virtual bool canCallMismatchedFunctionType() const { return true; } + + virtual ~CIRGenCXXABI(); + + void setCXXABIThisValue(CIRGenFunction &CGF, mlir::Value ThisPtr); + + // Determine if references to thread_local global variables can be made + // directly or require access through a thread wrapper function. + virtual bool usesThreadWrapperFunction(const VarDecl *VD) const = 0; + + /// Emit the code to initialize hidden members required to handle virtual + /// inheritance, if needed by the ABI. + virtual void + initializeHiddenVirtualInheritanceMembers(CIRGenFunction &CGF, + const CXXRecordDecl *RD) {} + + /// Emit a single constructor/destructor with the gien type from a C++ + /// constructor Decl. + virtual void buildCXXStructor(clang::GlobalDecl GD) = 0; + + virtual void buildRethrow(CIRGenFunction &CGF, bool isNoReturn) = 0; + virtual void buildThrow(CIRGenFunction &CGF, const CXXThrowExpr *E) = 0; +}; + +/// Creates and Itanium-family ABI +CIRGenCXXABI *CreateCIRGenItaniumCXXABI(CIRGenModule &CGM); + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp new file mode 100644 index 000000000000..8688d8dcfdd2 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp @@ -0,0 +1,1345 @@ +//===--- CIRGenCall.cpp - Encapsulate calling convention details ----------===// +// +// 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 wrap the information about a call or function +// definition used to handle ABI compliancy. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenBuilder.h" +#include "CIRGenCXXABI.h" +#include "CIRGenFunction.h" +#include "CIRGenFunctionInfo.h" +#include "CIRGenTypes.h" +#include "TargetInfo.h" + +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/Support/ErrorHandling.h" +#include + +#include "UnimplementedFeatureGuarding.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/SymbolTable.h" +#include "mlir/IR/Types.h" + +using namespace cir; +using namespace clang; + +CIRGenFunctionInfo *CIRGenFunctionInfo::create( + unsigned cirCC, bool instanceMethod, bool chainCall, + const FunctionType::ExtInfo &info, + llvm::ArrayRef paramInfos, CanQualType resultType, + llvm::ArrayRef argTypes, RequiredArgs required) { + assert(paramInfos.empty() || paramInfos.size() == argTypes.size()); + assert(!required.allowsOptionalArgs() || + required.getNumRequiredArgs() <= argTypes.size()); + + void *buffer = operator new(totalSizeToAlloc( + argTypes.size() + 1, paramInfos.size())); + + CIRGenFunctionInfo *FI = new (buffer) CIRGenFunctionInfo(); + FI->CallingConvention = cirCC; + FI->EffectiveCallingConvention = cirCC; + FI->ASTCallingConvention = info.getCC(); + FI->InstanceMethod = instanceMethod; + FI->ChainCall = chainCall; + FI->CmseNSCall = info.getCmseNSCall(); + FI->NoReturn = info.getNoReturn(); + FI->ReturnsRetained = info.getProducesResult(); + FI->NoCallerSavedRegs = info.getNoCallerSavedRegs(); + FI->NoCfCheck = info.getNoCfCheck(); + FI->Required = required; + FI->HasRegParm = info.getHasRegParm(); + FI->RegParm = info.getRegParm(); + FI->ArgStruct = nullptr; + FI->ArgStructAlign = 0; + FI->NumArgs = argTypes.size(); + FI->HasExtParameterInfos = !paramInfos.empty(); + FI->getArgsBuffer()[0].type = resultType; + for (unsigned i = 0; i < argTypes.size(); ++i) + FI->getArgsBuffer()[i + 1].type = argTypes[i]; + for (unsigned i = 0; i < paramInfos.size(); ++i) + FI->getExtParameterInfosBuffer()[i] = paramInfos[i]; + + return FI; +} + +namespace { + +/// Encapsulates information about the way function arguments from +/// CIRGenFunctionInfo should be passed to actual CIR function. +class ClangToCIRArgMapping { + static const unsigned InvalidIndex = ~0U; + unsigned InallocaArgNo; + unsigned SRetArgNo; + unsigned TotalCIRArgs; + + /// Arguments of CIR function corresponding to single Clang argument. + struct CIRArgs { + unsigned PaddingArgIndex = 0; + // Argument is expanded to CIR arguments at positions + // [FirstArgIndex, FirstArgIndex + NumberOfArgs). + unsigned FirstArgIndex = 0; + unsigned NumberOfArgs = 0; + + CIRArgs() + : PaddingArgIndex(InvalidIndex), FirstArgIndex(InvalidIndex), + NumberOfArgs(0) {} + }; + + SmallVector ArgInfo; + +public: + ClangToCIRArgMapping(const ASTContext &Context, const CIRGenFunctionInfo &FI, + bool OnlyRequiredArgs = false) + : InallocaArgNo(InvalidIndex), SRetArgNo(InvalidIndex), TotalCIRArgs(0), + ArgInfo(OnlyRequiredArgs ? FI.getNumRequiredArgs() : FI.arg_size()) { + construct(Context, FI, OnlyRequiredArgs); + } + + bool hasSRetArg() const { return SRetArgNo != InvalidIndex; } + + bool hasInallocaArg() const { return InallocaArgNo != InvalidIndex; } + + unsigned totalCIRArgs() const { return TotalCIRArgs; } + + bool hasPaddingArg(unsigned ArgNo) const { + assert(ArgNo < ArgInfo.size()); + return ArgInfo[ArgNo].PaddingArgIndex != InvalidIndex; + } + + /// Returns index of first CIR argument corresponding to ArgNo, and their + /// quantity. + std::pair getCIRArgs(unsigned ArgNo) const { + assert(ArgNo < ArgInfo.size()); + return std::make_pair(ArgInfo[ArgNo].FirstArgIndex, + ArgInfo[ArgNo].NumberOfArgs); + } + +private: + void construct(const ASTContext &Context, const CIRGenFunctionInfo &FI, + bool OnlyRequiredArgs); +}; + +void ClangToCIRArgMapping::construct(const ASTContext &Context, + const CIRGenFunctionInfo &FI, + bool OnlyRequiredArgs) { + unsigned CIRArgNo = 0; + bool SwapThisWithSRet = false; + const ABIArgInfo &RetAI = FI.getReturnInfo(); + + assert(RetAI.getKind() != ABIArgInfo::Indirect && "NYI"); + + unsigned ArgNo = 0; + unsigned NumArgs = OnlyRequiredArgs ? FI.getNumRequiredArgs() : FI.arg_size(); + for (CIRGenFunctionInfo::const_arg_iterator I = FI.arg_begin(); + ArgNo < NumArgs; ++I, ++ArgNo) { + assert(I != FI.arg_end()); + const ABIArgInfo &AI = I->info; + // Collect data about CIR arguments corresponding to Clang argument ArgNo. + auto &CIRArgs = ArgInfo[ArgNo]; + + assert(!AI.getPaddingType() && "NYI"); + + switch (AI.getKind()) { + default: + llvm_unreachable("NYI"); + case ABIArgInfo::Extend: + case ABIArgInfo::Direct: { + // Postpone splitting structs into elements since this makes it way + // more complicated for analysis to obtain information on the original + // arguments. + // + // TODO(cir): a LLVM lowering prepare pass should break this down into + // the appropriated pieces. + assert(!UnimplementedFeature::constructABIArgDirectExtend()); + CIRArgs.NumberOfArgs = 1; + break; + } + } + + if (CIRArgs.NumberOfArgs > 0) { + CIRArgs.FirstArgIndex = CIRArgNo; + CIRArgNo += CIRArgs.NumberOfArgs; + } + + assert(!SwapThisWithSRet && "NYI"); + } + assert(ArgNo == ArgInfo.size()); + + assert(!FI.usesInAlloca() && "NYI"); + + TotalCIRArgs = CIRArgNo; +} + +} // namespace + +static bool hasInAllocaArgs(CIRGenModule &CGM, CallingConv ExplicitCC, + ArrayRef ArgTypes) { + assert(ExplicitCC != CC_Swift && ExplicitCC != CC_SwiftAsync && "Swift NYI"); + assert(!CGM.getTarget().getCXXABI().isMicrosoft() && "MSABI NYI"); + + return false; +} + +mlir::cir::FuncType CIRGenTypes::GetFunctionType(GlobalDecl GD) { + const CIRGenFunctionInfo &FI = arrangeGlobalDeclaration(GD); + return GetFunctionType(FI); +} + +mlir::cir::FuncType CIRGenTypes::GetFunctionType(const CIRGenFunctionInfo &FI) { + bool Inserted = FunctionsBeingProcessed.insert(&FI).second; + (void)Inserted; + assert(Inserted && "Recursively being processed?"); + + mlir::Type resultType = nullptr; + const ABIArgInfo &retAI = FI.getReturnInfo(); + switch (retAI.getKind()) { + case ABIArgInfo::Ignore: + // TODO(CIR): This should probably be the None type from the builtin + // dialect. + resultType = nullptr; + break; + + case ABIArgInfo::Extend: + case ABIArgInfo::Direct: + resultType = retAI.getCoerceToType(); + break; + + default: + assert(false && "NYI"); + } + + ClangToCIRArgMapping CIRFunctionArgs(getContext(), FI, true); + SmallVector ArgTypes(CIRFunctionArgs.totalCIRArgs()); + + assert(!CIRFunctionArgs.hasSRetArg() && "NYI"); + assert(!CIRFunctionArgs.hasInallocaArg() && "NYI"); + + // Add in all of the required arguments. + unsigned ArgNo = 0; + CIRGenFunctionInfo::const_arg_iterator it = FI.arg_begin(), + ie = it + FI.getNumRequiredArgs(); + + for (; it != ie; ++it, ++ArgNo) { + const auto &ArgInfo = it->info; + + assert(!CIRFunctionArgs.hasPaddingArg(ArgNo) && "NYI"); + + unsigned FirstCIRArg, NumCIRArgs; + std::tie(FirstCIRArg, NumCIRArgs) = CIRFunctionArgs.getCIRArgs(ArgNo); + + switch (ArgInfo.getKind()) { + default: + llvm_unreachable("NYI"); + case ABIArgInfo::Extend: + case ABIArgInfo::Direct: { + mlir::Type argType = ArgInfo.getCoerceToType(); + // TODO: handle the test against llvm::StructType from codegen + assert(NumCIRArgs == 1); + ArgTypes[FirstCIRArg] = argType; + break; + } + } + } + + bool Erased = FunctionsBeingProcessed.erase(&FI); + (void)Erased; + assert(Erased && "Not in set?"); + + return mlir::cir::FuncType::get( + ArgTypes, (resultType ? resultType : Builder.getVoidTy()), + FI.isVariadic()); +} + +mlir::cir::FuncType CIRGenTypes::GetFunctionTypeForVTable(GlobalDecl GD) { + const CXXMethodDecl *MD = cast(GD.getDecl()); + const FunctionProtoType *FPT = MD->getType()->getAs(); + + if (!isFuncTypeConvertible(FPT)) { + llvm_unreachable("NYI"); + // return llvm::StructType::get(getLLVMContext()); + } + + return GetFunctionType(GD); +} + +CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &CGF) const { + if (isVirtual()) { + const CallExpr *CE = getVirtualCallExpr(); + return CGF.CGM.getCXXABI().getVirtualFunctionPointer( + CGF, getVirtualMethodDecl(), getThisAddress(), getVirtualFunctionType(), + CE ? CE->getBeginLoc() : SourceLocation()); + } + return *this; +} + +void CIRGenFunction::buildAggregateStore(mlir::Value Val, Address Dest, + bool DestIsVolatile) { + // In LLVM codegen: + // Function to store a first-class aggregate into memory. We prefer to + // store the elements rather than the aggregate to be more friendly to + // fast-isel. + // In CIR codegen: + // Emit the most simple cir.store possible (e.g. a store for a whole + // struct), which can later be broken down in other CIR levels (or prior + // to dialect codegen). + (void)DestIsVolatile; + builder.create(*currSrcLoc, Val, Dest.getPointer()); +} + +static Address emitAddressAtOffset(CIRGenFunction &CGF, Address addr, + const ABIArgInfo &info) { + if (unsigned offset = info.getDirectOffset()) { + llvm_unreachable("NYI"); + } + return addr; +} + +static void AddAttributesFromFunctionProtoType(ASTContext &Ctx, + const FunctionProtoType *FPT) { + if (!FPT) + return; + + if (!isUnresolvedExceptionSpec(FPT->getExceptionSpecType()) && + FPT->isNothrow()) + llvm_unreachable("NoUnwind NYI"); +} + +/// Construct the CIR attribute list of a function or call. +/// +/// When adding an attribute, please consider where it should be handled: +/// +/// - getDefaultFunctionAttributes is for attributes that are essentially +/// part of the global target configuration (but perhaps can be +/// overridden on a per-function basis). Adding attributes there +/// will cause them to also be set in frontends that build on Clang's +/// target-configuration logic, as well as for code defined in library +/// modules such as CUDA's libdevice. +/// +/// - ConstructAttributeList builds on top of getDefaultFunctionAttributes +/// and adds declaration-specific, convention-specific, and +/// frontend-specific logic. The last is of particular importance: +/// attributes that restrict how the frontend generates code must be +/// added here rather than getDefaultFunctionAttributes. +/// +void CIRGenModule::ConstructAttributeList( + StringRef Name, const CIRGenFunctionInfo &FI, CIRGenCalleeInfo CalleeInfo, + llvm::SmallSet &Attrs, bool AttrOnCallSite, + bool IsThunk) { + // Implementation Disclaimer + // + // UnimplementedFeature and asserts are used throughout the code to track + // unsupported and things not yet implemented. However, most of the content of + // this function is on detecting attributes, which doesn't not cope with + // existing approaches to track work because its too big. + // + // That said, for the most part, the approach here is very specific compared + // to the rest of CIRGen and attributes and other handling should be done upon + // demand. + + // Collect function CIR attributes from the CC lowering. + // TODO: NoReturn, cmse_nonsecure_call + + // Collect function CIR attributes from the callee prototype if we have one. + AddAttributesFromFunctionProtoType(astCtx, + CalleeInfo.getCalleeFunctionProtoType()); +} + +RValue CIRGenFunction::buildCall(const CIRGenFunctionInfo &CallInfo, + const CIRGenCallee &Callee, + ReturnValueSlot ReturnValue, + const CallArgList &CallArgs, + mlir::cir::CallOp *callOrInvoke, + bool IsMustTail, mlir::Location loc) { + auto builder = CGM.getBuilder(); + // FIXME: We no longer need the types from CallArgs; lift up and simplify + + assert(Callee.isOrdinary() || Callee.isVirtual()); + + // Handle struct-return functions by passing a pointer to the location that we + // would like to return info. + QualType RetTy = CallInfo.getReturnType(); + const auto &RetAI = CallInfo.getReturnInfo(); + + mlir::cir::FuncType CIRFuncTy = getTypes().GetFunctionType(CallInfo); + + const Decl *TargetDecl = Callee.getAbstractInfo().getCalleeDecl().getDecl(); + // This is not always tied to a FunctionDecl (e.g. builtins that are xformed + // into calls to other functions) + if (const FunctionDecl *FD = dyn_cast_or_null(TargetDecl)) { + // We can only guarantee that a function is called from the correct + // context/function based on the appropriate target attributes, + // so only check in the case where we have both always_inline and target + // since otherwise we could be making a conditional call after a check for + // the proper cpu features (and it won't cause code generation issues due to + // function based code generation). + if (TargetDecl->hasAttr() && + (TargetDecl->hasAttr() || + (CurFuncDecl && CurFuncDecl->hasAttr()))) { + // FIXME(cir): somehow refactor this function to use SourceLocation? + SourceLocation Loc; + checkTargetFeatures(Loc, FD); + } + + // Some architectures (such as x86-64) have the ABI changed based on + // attribute-target/features. Give them a chance to diagnose. + assert(!UnimplementedFeature::checkFunctionCallABI()); + } + + // TODO: add DNEBUG code + + // 1. Set up the arguments + + // If we're using inalloca, insert the allocation after the stack save. + // FIXME: Do this earlier rather than hacking it in here! + Address ArgMemory = Address::invalid(); + assert(!CallInfo.getArgStruct() && "NYI"); + + ClangToCIRArgMapping CIRFunctionArgs(CGM.getASTContext(), CallInfo); + SmallVector CIRCallArgs(CIRFunctionArgs.totalCIRArgs()); + + // If the call returns a temporary with struct return, create a temporary + // alloca to hold the result, unless one is given to us. + assert(!RetAI.isIndirect() && !RetAI.isInAlloca() && + !RetAI.isCoerceAndExpand() && "NYI"); + + // When passing arguments using temporary allocas, we need to add the + // appropriate lifetime markers. This vector keeps track of all the lifetime + // markers that need to be ended right after the call. + assert(!UnimplementedFeature::shouldEmitLifetimeMarkers() && "NYI"); + + // Translate all of the arguments as necessary to match the CIR lowering. + assert(CallInfo.arg_size() == CallArgs.size() && + "Mismatch between function signature & arguments."); + unsigned ArgNo = 0; + CIRGenFunctionInfo::const_arg_iterator info_it = CallInfo.arg_begin(); + for (CallArgList::const_iterator I = CallArgs.begin(), E = CallArgs.end(); + I != E; ++I, ++info_it, ++ArgNo) { + const ABIArgInfo &ArgInfo = info_it->info; + + // Insert a padding argument to ensure proper alignment. + assert(!CIRFunctionArgs.hasPaddingArg(ArgNo) && "Padding args NYI"); + + unsigned FirstCIRArg, NumCIRArgs; + std::tie(FirstCIRArg, NumCIRArgs) = CIRFunctionArgs.getCIRArgs(ArgNo); + + switch (ArgInfo.getKind()) { + case ABIArgInfo::Direct: { + if (!ArgInfo.getCoerceToType().isa() && + ArgInfo.getCoerceToType() == convertType(info_it->type) && + ArgInfo.getDirectOffset() == 0) { + assert(NumCIRArgs == 1); + mlir::Value V; + assert(!I->isAggregate() && "Aggregate NYI"); + V = I->getKnownRValue().getScalarVal(); + + assert(CallInfo.getExtParameterInfo(ArgNo).getABI() != + ParameterABI::SwiftErrorResult && + "swift NYI"); + + // We might have to widen integers, but we should never truncate. + if (ArgInfo.getCoerceToType() != V.getType() && + V.getType().isa()) + llvm_unreachable("NYI"); + + // If the argument doesn't match, perform a bitcast to coerce it. This + // can happen due to trivial type mismatches. + if (FirstCIRArg < CIRFuncTy.getNumInputs() && + V.getType() != CIRFuncTy.getInput(FirstCIRArg)) + V = builder.createBitcast(V, CIRFuncTy.getInput(FirstCIRArg)); + + CIRCallArgs[FirstCIRArg] = V; + break; + } + + // FIXME: Avoid the conversion through memory if possible. + Address Src = Address::invalid(); + if (!I->isAggregate()) { + llvm_unreachable("NYI"); + } else { + Src = I->hasLValue() ? I->getKnownLValue().getAddress() + : I->getKnownRValue().getAggregateAddress(); + } + + // If the value is offset in memory, apply the offset now. + Src = emitAddressAtOffset(*this, Src, ArgInfo); + + // Fast-isel and the optimizer generally like scalar values better than + // FCAs, so we flatten them if this is safe to do for this argument. + auto STy = dyn_cast(ArgInfo.getCoerceToType()); + if (STy && ArgInfo.isDirect() && ArgInfo.getCanBeFlattened()) { + auto SrcTy = Src.getElementType(); + // FIXME(cir): get proper location for each argument. + auto argLoc = loc; + + // If the source type is smaller than the destination type of the + // coerce-to logic, copy the source value into a temp alloca the size + // of the destination type to allow loading all of it. The bits past + // the source value are left undef. + // FIXME(cir): add data layout info and compare sizes instead of + // matching the types. + // + // uint64_t SrcSize = CGM.getDataLayout().getTypeAllocSize(SrcTy); + // uint64_t DstSize = CGM.getDataLayout().getTypeAllocSize(STy); + // if (SrcSize < DstSize) { + if (SrcTy != STy) + llvm_unreachable("NYI"); + else { + // FIXME(cir): this currently only runs when the types are different, + // but should be when alloc sizes are different, fix this as soon as + // datalayout gets introduced. + Src = builder.createElementBitCast(argLoc, Src, STy); + } + + // assert(NumCIRArgs == STy.getMembers().size()); + // In LLVMGen: Still only pass the struct without any gaps but mark it + // as such somehow. + // + // In CIRGen: Emit a load from the "whole" struct, + // which shall be broken later by some lowering step into multiple + // loads. + assert(NumCIRArgs == 1 && "dont break up arguments here!"); + CIRCallArgs[FirstCIRArg] = builder.createLoad(argLoc, Src); + } else { + llvm_unreachable("NYI"); + } + + break; + } + default: + assert(false && "Only Direct support so far"); + } + } + + const CIRGenCallee &ConcreteCallee = Callee.prepareConcreteCallee(*this); + auto CalleePtr = ConcreteCallee.getFunctionPointer(); + + // If we're using inalloca, set up that argument. + assert(!ArgMemory.isValid() && "inalloca NYI"); + + // 2. Prepare the function pointer. + + // TODO: simplifyVariadicCallee + + // 3. Perform the actual call. + + // TODO: Deactivate any cleanups that we're supposed to do immediately before + // the call. + // if (!CallArgs.getCleanupsToDeactivate().empty()) + // deactivateArgCleanupsBeforeCall(*this, CallArgs); + // TODO: Update the largest vector width if any arguments have vector types. + + // Compute the calling convention and attributes. + llvm::SmallSet Attrs; + StringRef FnName; + if (auto calleeFnOp = dyn_cast(CalleePtr)) + FnName = calleeFnOp.getName(); + CGM.ConstructAttributeList(FnName, CallInfo, Callee.getAbstractInfo(), Attrs, + /*AttrOnCallSite=*/true, + /*IsThunk=*/false); + + // TODO: strictfp + // TODO: Add call-site nomerge, noinline, always_inline attribute if exists. + + // Apply some call-site-specific attributes. + // TODO: work this into building the attribute set. + + // Apply always_inline to all calls within flatten functions. + // FIXME: should this really take priority over __try, below? + // assert(!CurCodeDecl->hasAttr() && + // !TargetDecl->hasAttr() && "NYI"); + + // Disable inlining inside SEH __try blocks. + if (isSEHTryScope()) + llvm_unreachable("NYI"); + + // Decide whether to use a call or an invoke. + bool CannotThrow; + if (currentFunctionUsesSEHTry()) { + // SEH cares about asynchronous exceptions, so everything can "throw." + CannotThrow = false; + } else if (isCleanupPadScope() && + EHPersonality::get(*this).isMSVCXXPersonality()) { + // The MSVC++ personality will implicitly terminate the program if an + // exception is thrown during a cleanup outside of a try/catch. + // We don't need to model anything in IR to get this behavior. + CannotThrow = true; + } else { + // FIXME(cir): pass down nounwind attribute + CannotThrow = false; + } + (void)CannotThrow; + + // In LLVM this contains the basic block, in CIR we solely track for now. + bool InvokeDest = getInvokeDest(); + (void)InvokeDest; + + // TODO: UnusedReturnSizePtr + if (const FunctionDecl *FD = dyn_cast_or_null(CurFuncDecl)) + assert(!FD->hasAttr() && "NYI"); + + // TODO: alignment attributes + + // Emit the actual call op. + auto callLoc = loc; + assert(builder.getInsertionBlock() && "expected valid basic block"); + + mlir::cir::CallOp theCall; + if (auto fnOp = dyn_cast(CalleePtr)) { + assert(fnOp && "only direct call supported"); + theCall = builder.create(callLoc, fnOp, CIRCallArgs); + } else if (auto loadOp = dyn_cast(CalleePtr)) { + theCall = builder.create(callLoc, loadOp->getResult(0), + CIRFuncTy, CIRCallArgs); + } else if (auto getGlobalOp = dyn_cast(CalleePtr)) { + // FIXME(cir): This peephole optimization to avoids indirect calls for + // builtins. This should be fixed in the builting declaration instead by not + // emitting an unecessary get_global in the first place. + auto *globalOp = mlir::SymbolTable::lookupSymbolIn(CGM.getModule(), + getGlobalOp.getName()); + assert(getGlobalOp && "undefined global function"); + auto callee = llvm::dyn_cast(globalOp); + assert(callee && "operation is not a function"); + theCall = builder.create(callLoc, callee, CIRCallArgs); + } else { + llvm_unreachable("expected call variant to be handled"); + } + + if (callOrInvoke) + callOrInvoke = &theCall; + + if (const auto *FD = dyn_cast_or_null(CurFuncDecl)) + assert(!FD->getAttr() && "NYI"); + + // TODO: set attributes on callop + // assert(!theCall.getResults().getType().front().isSignlessInteger() && + // "Vector NYI"); + // TODO: LLVM models indirect calls via a null callee, how should we do this? + assert(!CGM.getLangOpts().ObjCAutoRefCount && "Not supported"); + assert((!TargetDecl || !TargetDecl->hasAttr()) && "NYI"); + assert(!getDebugInfo() && "No debug info yet"); + assert((!TargetDecl || !TargetDecl->hasAttr()) && "NYI"); + + // 4. Finish the call. + + // If the call doesn't return, finish the basic block and clear the insertion + // point; this allows the rest of CIRGen to discard unreachable code. + // TODO: figure out how to support doesNotReturn + + assert(!IsMustTail && "NYI"); + + // TODO: figure out writebacks? seems like ObjC only __autorelease + + // TODO: cleanup argument memory at the end + + // Extract the return value. + RValue ret = [&] { + switch (RetAI.getKind()) { + case ABIArgInfo::Direct: { + mlir::Type RetCIRTy = convertType(RetTy); + if (RetAI.getCoerceToType() == RetCIRTy && RetAI.getDirectOffset() == 0) { + switch (getEvaluationKind(RetTy)) { + case TEK_Aggregate: { + Address DestPtr = ReturnValue.getValue(); + bool DestIsVolatile = ReturnValue.isVolatile(); + + if (!DestPtr.isValid()) { + DestPtr = CreateMemTemp(RetTy, callLoc, getCounterAggTmpAsString()); + DestIsVolatile = false; + } + + auto Results = theCall.getResults(); + assert(Results.size() <= 1 && "multiple returns NYI"); + + SourceLocRAIIObject Loc{*this, callLoc}; + buildAggregateStore(Results[0], DestPtr, DestIsVolatile); + return RValue::getAggregate(DestPtr); + } + case TEK_Scalar: { + // If the argument doesn't match, perform a bitcast to coerce it. This + // can happen due to trivial type mismatches. + auto Results = theCall.getResults(); + assert(Results.size() <= 1 && "multiple returns NYI"); + assert(Results[0].getType() == RetCIRTy && "Bitcast support NYI"); + return RValue::get(Results[0]); + } + default: + llvm_unreachable("NYI"); + } + } else { + llvm_unreachable("No other forms implemented yet."); + } + } + + case ABIArgInfo::Ignore: + // If we are ignoring an argument that had a result, make sure to + // construct the appropriate return value for our caller. + return GetUndefRValue(RetTy); + + default: + llvm_unreachable("NYI"); + } + + llvm_unreachable("NYI"); + return RValue{}; + }(); + + // TODO: implement assumed_aligned + + // TODO: implement lifetime extensions + + assert(RetTy.isDestructedType() != QualType::DK_nontrivial_c_struct && "NYI"); + + return ret; +} + +RValue CIRGenFunction::GetUndefRValue(QualType Ty) { + assert(Ty->isVoidType() && "Only VoidType supported so far."); + return RValue::get(nullptr); +} + +void CIRGenFunction::buildCallArg(CallArgList &args, const Expr *E, + QualType type) { + // TODO: Add the DisableDebugLocationUpdates helper + assert(!dyn_cast(E) && "NYI"); + + assert(type->isReferenceType() == E->isGLValue() && + "reference binding to unmaterialized r-value!"); + + if (E->isGLValue()) { + assert(E->getObjectKind() == OK_Ordinary); + return args.add(buildReferenceBindingToExpr(E), type); + } + + bool HasAggregateEvalKind = hasAggregateEvaluationKind(type); + + // In the Microsoft C++ ABI, aggregate arguments are destructed by the callee. + // However, we still have to push an EH-only cleanup in case we unwind before + // we make it to the call. + if (type->isRecordType() && + type->castAs()->getDecl()->isParamDestroyedInCallee()) { + llvm_unreachable("Microsoft C++ ABI is NYI"); + } + + if (HasAggregateEvalKind && isa(E) && + cast(E)->getCastKind() == CK_LValueToRValue) { + LValue L = buildLValue(cast(E)->getSubExpr()); + assert(L.isSimple()); + args.addUncopiedAggregate(L, type); + return; + } + + args.add(buildAnyExprToTemp(E), type); +} + +QualType CIRGenFunction::getVarArgType(const Expr *Arg) { + // System headers on Windows define NULL to 0 instead of 0LL on Win64. MSVC + // implicitly widens null pointer constants that are arguments to varargs + // functions to pointer-sized ints. + if (!getTarget().getTriple().isOSWindows()) + return Arg->getType(); + + if (Arg->getType()->isIntegerType() && + getContext().getTypeSize(Arg->getType()) < + getContext().getTargetInfo().getPointerWidth(LangAS::Default) && + Arg->isNullPointerConstant(getContext(), + Expr::NPC_ValueDependentIsNotNull)) { + return getContext().getIntPtrType(); + } + + return Arg->getType(); +} + +/// Similar to buildAnyExpr(), however, the result will always be accessible +/// even if no aggregate location is provided. +RValue CIRGenFunction::buildAnyExprToTemp(const Expr *E) { + AggValueSlot AggSlot = AggValueSlot::ignored(); + + if (hasAggregateEvaluationKind(E->getType())) + AggSlot = CreateAggTemp(E->getType(), getLoc(E->getSourceRange()), + getCounterAggTmpAsString()); + + return buildAnyExpr(E, AggSlot); +} + +void CIRGenFunction::buildCallArgs( + CallArgList &Args, PrototypeWrapper Prototype, + llvm::iterator_range ArgRange, + AbstractCallee AC, unsigned ParamsToSkip, EvaluationOrder Order) { + + llvm::SmallVector ArgTypes; + + assert((ParamsToSkip == 0 || Prototype.P) && + "Can't skip parameters if type info is not provided"); + + // This variable only captures *explicitly* written conventions, not those + // applied by default via command line flags or target defaults, such as + // thiscall, appcs, stdcall via -mrtd, etc. Computing that correctly would + // require knowing if this is a C++ instance method or being able to see + // unprotyped FunctionTypes. + CallingConv ExplicitCC = CC_C; + + // First, if a prototype was provided, use those argument types. + bool IsVariadic = false; + if (Prototype.P) { + const auto *MD = Prototype.P.dyn_cast(); + assert(!MD && "ObjCMethodDecl NYI"); + + const auto *FPT = Prototype.P.get(); + IsVariadic = FPT->isVariadic(); + ExplicitCC = FPT->getExtInfo().getCC(); + ArgTypes.assign(FPT->param_type_begin() + ParamsToSkip, + FPT->param_type_end()); + } + + // If we still have any arguments, emit them using the type of the argument. + for (auto *A : llvm::drop_begin(ArgRange, ArgTypes.size())) + ArgTypes.push_back(IsVariadic ? getVarArgType(A) : A->getType()); + assert((int)ArgTypes.size() == (ArgRange.end() - ArgRange.begin())); + + // We must evaluate arguments from right to left in the MS C++ ABI, because + // arguments are destroyed left to right in the callee. As a special case, + // there are certain language constructs taht require left-to-right + // evaluation, and in those cases we consider the evaluation order requirement + // to trump the "destruction order is reverse construction order" guarantee. + bool LeftToRight = true; + assert(!CGM.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee() && + "MSABI NYI"); + assert(!hasInAllocaArgs(CGM, ExplicitCC, ArgTypes) && "NYI"); + + auto MaybeEmitImplicitObjectSize = [&](unsigned I, const Expr *Arg, + RValue EmittedArg) { + if (!AC.hasFunctionDecl() || I >= AC.getNumParams()) + return; + auto *PS = AC.getParamDecl(I)->getAttr(); + if (PS == nullptr) + return; + + const auto &Context = getContext(); + auto SizeTy = Context.getSizeType(); + auto T = builder.getUIntNTy(Context.getTypeSize(SizeTy)); + assert(EmittedArg.getScalarVal() && "We emitted nothing for the arg?"); + auto V = evaluateOrEmitBuiltinObjectSize( + Arg, PS->getType(), T, EmittedArg.getScalarVal(), PS->isDynamic()); + Args.add(RValue::get(V), SizeTy); + // If we're emitting args in reverse, be sure to do so with + // pass_object_size, as well. + if (!LeftToRight) + std::swap(Args.back(), *(&Args.back() - 1)); + }; + + // Evaluate each argument in the appropriate order. + size_t CallArgsStart = Args.size(); + for (unsigned I = 0, E = ArgTypes.size(); I != E; ++I) { + unsigned Idx = LeftToRight ? I : E - I - 1; + CallExpr::const_arg_iterator Arg = ArgRange.begin() + Idx; + unsigned InitialArgSize = Args.size(); + assert(!isa(*Arg) && "NYI"); + assert(!isa_and_nonnull(AC.getDecl()) && "NYI"); + + buildCallArg(Args, *Arg, ArgTypes[Idx]); + // In particular, we depend on it being the last arg in Args, and the + // objectsize bits depend on there only being one arg if !LeftToRight. + assert(InitialArgSize + 1 == Args.size() && + "The code below depends on only adding one arg per buildCallArg"); + (void)InitialArgSize; + // Since pointer argument are never emitted as LValue, it is safe to emit + // non-null argument check for r-value only. + if (!Args.back().hasLValue()) { + RValue RVArg = Args.back().getKnownRValue(); + assert(!SanOpts.has(SanitizerKind::NonnullAttribute) && "Sanitizers NYI"); + assert(!SanOpts.has(SanitizerKind::NullabilityArg) && "Sanitizers NYI"); + // @llvm.objectsize should never have side-effects and shouldn't need + // destruction/cleanups, so we can safely "emit" it after its arg, + // regardless of right-to-leftness + MaybeEmitImplicitObjectSize(Idx, *Arg, RVArg); + } + } + + if (!LeftToRight) { + // Un-reverse the arguments we just evaluated so they match up with the CIR + // function. + std::reverse(Args.begin() + CallArgsStart, Args.end()); + } +} + +/// Returns the canonical formal type of the given C++ method. +static CanQual GetFormalType(const CXXMethodDecl *MD) { + return MD->getType() + ->getCanonicalTypeUnqualified() + .getAs(); +} + +/// TODO(cir): this should be shared with LLVM codegen +static void addExtParameterInfosForCall( + llvm::SmallVectorImpl ¶mInfos, + const FunctionProtoType *proto, unsigned prefixArgs, unsigned totalArgs) { + assert(proto->hasExtParameterInfos()); + assert(paramInfos.size() <= prefixArgs); + assert(proto->getNumParams() + prefixArgs <= totalArgs); + + paramInfos.reserve(totalArgs); + + // Add default infos for any prefix args that don't already have infos. + paramInfos.resize(prefixArgs); + + // Add infos for the prototype. + for (const auto &ParamInfo : proto->getExtParameterInfos()) { + paramInfos.push_back(ParamInfo); + // pass_object_size params have no parameter info. + if (ParamInfo.hasPassObjectSize()) + paramInfos.emplace_back(); + } + + assert(paramInfos.size() <= totalArgs && + "Did we forget to insert pass_object_size args?"); + // Add default infos for the variadic and/or suffix arguments. + paramInfos.resize(totalArgs); +} + +/// Adds the formal parameters in FPT to the given prefix. If any parameter in +/// FPT has pass_object_size_attrs, then we'll add parameters for those, too. +/// TODO(cir): this should be shared with LLVM codegen +static void appendParameterTypes( + const CIRGenTypes &CGT, SmallVectorImpl &prefix, + SmallVectorImpl ¶mInfos, + CanQual FPT) { + // Fast path: don't touch param info if we don't need to. + if (!FPT->hasExtParameterInfos()) { + assert(paramInfos.empty() && + "We have paramInfos, but the prototype doesn't?"); + prefix.append(FPT->param_type_begin(), FPT->param_type_end()); + return; + } + + unsigned PrefixSize = prefix.size(); + // In the vast majority of cases, we'll have precisely FPT->getNumParams() + // parameters; the only thing that can change this is the presence of + // pass_object_size. So, we preallocate for the common case. + prefix.reserve(prefix.size() + FPT->getNumParams()); + + auto ExtInfos = FPT->getExtParameterInfos(); + assert(ExtInfos.size() == FPT->getNumParams()); + for (unsigned I = 0, E = FPT->getNumParams(); I != E; ++I) { + prefix.push_back(FPT->getParamType(I)); + if (ExtInfos[I].hasPassObjectSize()) + prefix.push_back(CGT.getContext().getSizeType()); + } + + addExtParameterInfosForCall(paramInfos, FPT.getTypePtr(), PrefixSize, + prefix.size()); +} + +const CIRGenFunctionInfo & +CIRGenTypes::arrangeCXXStructorDeclaration(GlobalDecl GD) { + auto *MD = cast(GD.getDecl()); + + llvm::SmallVector argTypes; + SmallVector paramInfos; + argTypes.push_back(DeriveThisType(MD->getParent(), MD)); + + bool PassParams = true; + + if (auto *CD = dyn_cast(MD)) { + // A base class inheriting constructor doesn't get forwarded arguments + // needed to construct a virtual base (or base class thereof) + assert(!CD->getInheritedConstructor() && "Inheritance NYI"); + } + + CanQual FTP = GetFormalType(MD); + + if (PassParams) + appendParameterTypes(*this, argTypes, paramInfos, FTP); + + assert(paramInfos.empty() && "NYI"); + + assert(!MD->isVariadic() && "Variadic fns NYI"); + RequiredArgs required = RequiredArgs::All; + (void)required; + + FunctionType::ExtInfo extInfo = FTP->getExtInfo(); + + assert(!TheCXXABI.HasThisReturn(GD) && "NYI"); + + CanQualType resultType = Context.VoidTy; + (void)resultType; + + return arrangeCIRFunctionInfo(resultType, /*instanceMethod=*/true, + /*chainCall=*/false, argTypes, extInfo, + paramInfos, required); +} + +/// Derives the 'this' type for CIRGen purposes, i.e. ignoring method CVR +/// qualification. Either or both of RD and MD may be null. A null RD indicates +/// that there is no meaningful 'this' type, and a null MD can occur when +/// calling a method pointer. +CanQualType CIRGenTypes::DeriveThisType(const CXXRecordDecl *RD, + const CXXMethodDecl *MD) { + QualType RecTy; + if (RD) + RecTy = getContext().getTagDeclType(RD)->getCanonicalTypeInternal(); + else + assert(false && "CXXMethodDecl NYI"); + + if (MD) + RecTy = getContext().getAddrSpaceQualType( + RecTy, MD->getMethodQualifiers().getAddressSpace()); + return getContext().getPointerType(CanQualType::CreateUnsafe(RecTy)); +} + +/// Arrange the CIR function layout for a value of the given function type, on +/// top of any implicit parameters already stored. +static const CIRGenFunctionInfo & +arrangeCIRFunctionInfo(CIRGenTypes &CGT, bool instanceMethod, + SmallVectorImpl &prefix, + CanQual FTP) { + SmallVector paramInfos; + RequiredArgs Required = RequiredArgs::forPrototypePlus(FTP, prefix.size()); + // FIXME: Kill copy. -- from codegen + appendParameterTypes(CGT, prefix, paramInfos, FTP); + CanQualType resultType = FTP->getReturnType().getUnqualifiedType(); + + return CGT.arrangeCIRFunctionInfo(resultType, instanceMethod, + /*chainCall=*/false, prefix, + FTP->getExtInfo(), paramInfos, Required); +} + +/// Arrange the argument and result information for a value of the given +/// freestanding function type. +const CIRGenFunctionInfo & +CIRGenTypes::arrangeFreeFunctionType(CanQual FTP) { + SmallVector argTypes; + return ::arrangeCIRFunctionInfo(*this, /*instanceMethod=*/false, argTypes, + FTP); +} + +/// Arrange the argument and result information for a value of the given +/// unprototyped freestanding function type. +const CIRGenFunctionInfo & +CIRGenTypes::arrangeFreeFunctionType(CanQual FTNP) { + // When translating an unprototyped function type, always use a + // variadic type. + return arrangeCIRFunctionInfo(FTNP->getReturnType().getUnqualifiedType(), + /*instanceMethod=*/false, + /*chainCall=*/false, std::nullopt, + FTNP->getExtInfo(), {}, RequiredArgs(0)); +} + +/// Arrange a call to a C++ method, passing the given arguments. +/// +/// ExtraPrefixArgs is the number of ABI-specific args passed after the `this` +/// parameter. +/// ExtraSuffixArgs is the number of ABI-specific args passed at the end of +/// args. +/// PassProtoArgs indicates whether `args` has args for the parameters in the +/// given CXXConstructorDecl. +const CIRGenFunctionInfo &CIRGenTypes::arrangeCXXConstructorCall( + const CallArgList &Args, const CXXConstructorDecl *D, CXXCtorType CtorKind, + unsigned ExtraPrefixArgs, unsigned ExtraSuffixArgs, bool PassProtoArgs) { + + // FIXME: Kill copy. + llvm::SmallVector ArgTypes; + for (const auto &Arg : Args) + ArgTypes.push_back(Context.getCanonicalParamType(Arg.Ty)); + + // +1 for implicit this, which should always be args[0] + unsigned TotalPrefixArgs = 1 + ExtraPrefixArgs; + + CanQual FPT = GetFormalType(D); + RequiredArgs Required = PassProtoArgs + ? RequiredArgs::forPrototypePlus( + FPT, TotalPrefixArgs + ExtraSuffixArgs) + : RequiredArgs::All; + + GlobalDecl GD(D, CtorKind); + assert(!TheCXXABI.HasThisReturn(GD) && "ThisReturn NYI"); + assert(!TheCXXABI.hasMostDerivedReturn(GD) && "Most derived return NYI"); + CanQualType ResultType = Context.VoidTy; + + FunctionType::ExtInfo Info = FPT->getExtInfo(); + llvm::SmallVector ParamInfos; + // If the prototype args are elided, we should onlyy have ABI-specific args, + // which never have param info. + assert(!FPT->hasExtParameterInfos() && "NYI"); + + return arrangeCIRFunctionInfo(ResultType, /*instanceMethod=*/true, + /*chainCall=*/false, ArgTypes, Info, ParamInfos, + Required); +} + +bool CIRGenTypes::inheritingCtorHasParams(const InheritedConstructor &Inherited, + CXXCtorType Type) { + + // Parameters are unnecessary if we're constructing a base class subobject and + // the inherited constructor lives in a virtual base. + return Type == Ctor_Complete || + !Inherited.getShadowDecl()->constructsVirtualBase() || + !Target.getCXXABI().hasConstructorVariants(); +} + +bool CIRGenModule::MayDropFunctionReturn(const ASTContext &Context, + QualType ReturnType) { + // We can't just disard the return value for a record type with a complex + // destructor or a non-trivially copyable type. + if (const RecordType *RT = + ReturnType.getCanonicalType()->getAs()) { + llvm_unreachable("NYI"); + } + + return ReturnType.isTriviallyCopyableType(Context); +} + +static bool isInAllocaArgument(CIRGenCXXABI &ABI, QualType type) { + const auto *RD = type->getAsCXXRecordDecl(); + return RD && + ABI.getRecordArgABI(RD) == CIRGenCXXABI::RecordArgABI::DirectInMemory; +} + +void CIRGenFunction::buildDelegateCallArg(CallArgList &args, + const VarDecl *param, + SourceLocation loc) { + // StartFunction converted the ABI-lowered parameter(s) into a local alloca. + // We need to turn that into an r-value suitable for buildCall + Address local = GetAddrOfLocalVar(param); + + QualType type = param->getType(); + + if (isInAllocaArgument(CGM.getCXXABI(), type)) { + llvm_unreachable("NYI"); + } + + // GetAddrOfLocalVar returns a pointer-to-pointer for references, but the + // argument needs to be the original pointer. + if (type->isReferenceType()) { + args.add( + RValue::get(builder.createLoad(getLoc(param->getSourceRange()), local)), + type); + } else if (getLangOpts().ObjCAutoRefCount) { + llvm_unreachable("NYI"); + // For the most part, we just need to load the alloca, except that aggregate + // r-values are actually pointers to temporaries. + } else { + args.add(convertTempToRValue(local, type, loc), type); + } + + // Deactivate the cleanup for the callee-destructed param that was pushed. + if (type->isRecordType() && !CurFuncIsThunk && + type->castAs()->getDecl()->isParamDestroyedInCallee() && + param->needsDestruction(getContext())) { + llvm_unreachable("NYI"); + } +} + +/// Returns the "extra-canonicalized" return type, which discards qualifiers on +/// the return type. Codegen doesn't care about them, and it makes ABI code a +/// little easier to be able to assume that all parameter and return types are +/// top-level unqualified. +/// FIXME(CIR): This should be a common helper extracted from CodeGen +static CanQualType GetReturnType(QualType RetTy) { + return RetTy->getCanonicalTypeUnqualified().getUnqualifiedType(); +} + +/// Arrange a call as unto a free function, except possibly with an additional +/// number of formal parameters considered required. +static const CIRGenFunctionInfo & +arrangeFreeFunctionLikeCall(CIRGenTypes &CGT, CIRGenModule &CGM, + const CallArgList &args, const FunctionType *fnType, + unsigned numExtraRequiredArgs, bool chainCall) { + assert(args.size() >= numExtraRequiredArgs); + assert(!chainCall && "Chain call NYI"); + + llvm::SmallVector paramInfos; + + // In most cases, there are no optional arguments. + RequiredArgs required = RequiredArgs::All; + + // If we have a variadic prototype, the required arguments are the + // extra prefix plus the arguments in the prototype. + if (const FunctionProtoType *proto = dyn_cast(fnType)) { + if (proto->isVariadic()) + required = RequiredArgs::forPrototypePlus(proto, numExtraRequiredArgs); + + if (proto->hasExtParameterInfos()) + addExtParameterInfosForCall(paramInfos, proto, numExtraRequiredArgs, + args.size()); + } else if (llvm::isa(fnType)) { + assert(!UnimplementedFeature::targetCodeGenInfoIsProtoCallVariadic()); + required = RequiredArgs(args.size()); + } + + // FIXME: Kill copy. + SmallVector argTypes; + for (const auto &arg : args) + argTypes.push_back(CGT.getContext().getCanonicalParamType(arg.Ty)); + return CGT.arrangeCIRFunctionInfo( + GetReturnType(fnType->getReturnType()), /*instanceMethod=*/false, + chainCall, argTypes, fnType->getExtInfo(), paramInfos, required); +} + +static llvm::SmallVector +getArgTypesForCall(ASTContext &ctx, const CallArgList &args) { + llvm::SmallVector argTypes; + for (auto &arg : args) + argTypes.push_back(ctx.getCanonicalParamType(arg.Ty)); + return argTypes; +} + +static llvm::SmallVector +getExtParameterInfosForCall(const FunctionProtoType *proto, unsigned prefixArgs, + unsigned totalArgs) { + llvm::SmallVector result; + if (proto->hasExtParameterInfos()) { + llvm_unreachable("NYI"); + } + return result; +} + +/// Arrange a call to a C++ method, passing the given arguments. +/// +/// numPrefixArgs is the number of the ABI-specific prefix arguments we have. It +/// does not count `this`. +const CIRGenFunctionInfo &CIRGenTypes::arrangeCXXMethodCall( + const CallArgList &args, const FunctionProtoType *proto, + RequiredArgs required, unsigned numPrefixArgs) { + assert(numPrefixArgs + 1 <= args.size() && + "Emitting a call with less args than the required prefix?"); + // Add one to account for `this`. It is a bit awkard here, but we don't count + // `this` in similar places elsewhere. + auto paramInfos = + getExtParameterInfosForCall(proto, numPrefixArgs + 1, args.size()); + + // FIXME: Kill copy. + auto argTypes = getArgTypesForCall(Context, args); + + auto info = proto->getExtInfo(); + return arrangeCIRFunctionInfo( + GetReturnType(proto->getReturnType()), /*instanceMethod=*/true, + /*chainCall=*/false, argTypes, info, paramInfos, required); +} + +/// Figure out the rules for calling a function with the given formal type using +/// the given arguments. The arguments are necessary because the function might +/// be unprototyped, in which case it's target-dependent in crazy ways. +const CIRGenFunctionInfo &CIRGenTypes::arrangeFreeFunctionCall( + const CallArgList &args, const FunctionType *fnType, bool ChainCall) { + assert(!ChainCall && "ChainCall NYI"); + return arrangeFreeFunctionLikeCall(*this, CGM, args, fnType, + ChainCall ? 1 : 0, ChainCall); +} + +/// Set calling convention for CUDA/HIP kernel. +static void setCUDAKernelCallingConvention(CanQualType &FTy, CIRGenModule &CGM, + const FunctionDecl *FD) { + if (FD->hasAttr()) { + llvm_unreachable("NYI"); + } +} + +/// Arrange the argument and result information for a declaration or definition +/// of the given C++ non-static member function. The member function must be an +/// ordinary function, i.e. not a constructor or destructor. +const CIRGenFunctionInfo & +CIRGenTypes::arrangeCXXMethodDeclaration(const CXXMethodDecl *MD) { + assert(!isa(MD) && "wrong method for constructors!"); + assert(!isa(MD) && "wrong method for destructors!"); + + CanQualType FT = GetFormalType(MD).getAs(); + setCUDAKernelCallingConvention(FT, CGM, MD); + auto prototype = FT.getAs(); + + if (MD->isInstance()) { + // The abstarct case is perfectly fine. + auto *ThisType = TheCXXABI.getThisArgumentTypeForMethod(MD); + return arrangeCXXMethodType(ThisType, prototype.getTypePtr(), MD); + } + + return arrangeFreeFunctionType(prototype); +} + +/// Arrange the argument and result information for a call to an unknown C++ +/// non-static member function of the given abstract type. (A null RD means we +/// don't have any meaningful "this" argument type, so fall back to a generic +/// pointer type). The member fucntion must be an ordinary function, i.e. not a +/// constructor or destructor. +const CIRGenFunctionInfo & +CIRGenTypes::arrangeCXXMethodType(const CXXRecordDecl *RD, + const FunctionProtoType *FTP, + const CXXMethodDecl *MD) { + llvm::SmallVector argTypes; + + // Add the 'this' pointer. + argTypes.push_back(DeriveThisType(RD, MD)); + + return ::arrangeCIRFunctionInfo( + *this, true, argTypes, + FTP->getCanonicalTypeUnqualified().getAs()); +} + +/// Arrange the argument and result information for the declaration or +/// definition of the given function. +const CIRGenFunctionInfo & +CIRGenTypes::arrangeFunctionDeclaration(const FunctionDecl *FD) { + if (const auto *MD = dyn_cast(FD)) + if (MD->isInstance()) + return arrangeCXXMethodDeclaration(MD); + + auto FTy = FD->getType()->getCanonicalTypeUnqualified(); + + assert(isa(FTy)); + // TODO: setCUDAKernelCallingConvention + + // When declaring a function without a prototype, always use a non-variadic + // type. + if (CanQual noProto = FTy.getAs()) { + return arrangeCIRFunctionInfo(noProto->getReturnType(), + /*instanceMethod=*/false, + /*chainCall=*/false, std::nullopt, + noProto->getExtInfo(), {}, RequiredArgs::All); + } + + return arrangeFreeFunctionType(FTy.castAs()); +} + +RValue CallArg::getRValue(CIRGenFunction &CGF, mlir::Location loc) const { + if (!HasLV) + return RV; + LValue Copy = CGF.makeAddrLValue(CGF.CreateMemTemp(Ty, loc), Ty); + CGF.buildAggregateCopy(Copy, LV, Ty, AggValueSlot::DoesNotOverlap, + LV.isVolatile()); + IsUsed = true; + return RValue::getAggregate(Copy.getAddress()); +} + +void CIRGenFunction::buildNonNullArgCheck(RValue RV, QualType ArgType, + SourceLocation ArgLoc, + AbstractCallee AC, unsigned ParmNum) { + if (!AC.getDecl() || !(SanOpts.has(SanitizerKind::NonnullAttribute) || + SanOpts.has(SanitizerKind::NullabilityArg))) + return; + llvm_unreachable("non-null arg check is NYI"); +} + +/* VarArg handling */ + +// FIXME(cir): This completely abstracts away the ABI with a generic CIR Op. We +// need to decide how to handle va_arg target-specific codegen. +mlir::Value CIRGenFunction::buildVAArg(VAArgExpr *VE, Address &VAListAddr) { + assert(!VE->isMicrosoftABI() && "NYI"); + auto loc = CGM.getLoc(VE->getExprLoc()); + auto type = ConvertType(VE->getType()); + auto vaList = buildVAListRef(VE->getSubExpr()).getPointer(); + return builder.create(loc, type, vaList); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.h b/clang/lib/CIR/CodeGen/CIRGenCall.h new file mode 100644 index 000000000000..80941919e2e1 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCall.h @@ -0,0 +1,291 @@ +//===----- CIRGenCall.h - Encapsulate calling convention details ----------===// +// +// 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 wrap the information about a call or function +// definition used to handle ABI compliancy. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CODEGEN_CIRGENCALL_H +#define LLVM_CLANG_LIB_CODEGEN_CIRGENCALL_H + +#include "CIRGenValue.h" + +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Type.h" + +#include "llvm/ADT/SmallVector.h" + +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +#include "mlir/IR/BuiltinOps.h" + +namespace cir { +class CIRGenFunction; + +/// Abstract information about a function or function prototype. +class CIRGenCalleeInfo { + const clang::FunctionProtoType *CalleeProtoTy; + clang::GlobalDecl CalleeDecl; + +public: + explicit CIRGenCalleeInfo() : CalleeProtoTy(nullptr), CalleeDecl() {} + CIRGenCalleeInfo(const clang::FunctionProtoType *calleeProtoTy, + clang::GlobalDecl calleeDecl) + : CalleeProtoTy(calleeProtoTy), CalleeDecl(calleeDecl) {} + CIRGenCalleeInfo(const clang::FunctionProtoType *calleeProtoTy) + : CalleeProtoTy(calleeProtoTy) {} + CIRGenCalleeInfo(clang::GlobalDecl calleeDecl) + : CalleeProtoTy(nullptr), CalleeDecl(calleeDecl) {} + + const clang::FunctionProtoType *getCalleeFunctionProtoType() const { + return CalleeProtoTy; + } + const clang::GlobalDecl getCalleeDecl() const { return CalleeDecl; } +}; + +/// All available information about a concrete callee. +class CIRGenCallee { + enum class SpecialKind : uintptr_t { + Invalid, + Builtin, + PsuedoDestructor, + Virtual, + + Last = Virtual + }; + + struct BuiltinInfoStorage { + const clang::FunctionDecl *Decl; + unsigned ID; + }; + struct PseudoDestructorInfoStorage { + const clang::CXXPseudoDestructorExpr *Expr; + }; + struct VirtualInfoStorage { + const clang::CallExpr *CE; + clang::GlobalDecl MD; + Address Addr; + mlir::cir::FuncType FTy; + }; + + SpecialKind KindOrFunctionPointer; + + union { + CIRGenCalleeInfo AbstractInfo; + BuiltinInfoStorage BuiltinInfo; + PseudoDestructorInfoStorage PseudoDestructorInfo; + VirtualInfoStorage VirtualInfo; + }; + + explicit CIRGenCallee(SpecialKind kind) : KindOrFunctionPointer(kind) {} + +public: + CIRGenCallee() : KindOrFunctionPointer(SpecialKind::Invalid) {} + + // Construct a callee. Call this constructor directly when this isn't a direct + // call. + CIRGenCallee(const CIRGenCalleeInfo &abstractInfo, + mlir::Operation *functionPtr) + : KindOrFunctionPointer( + SpecialKind(reinterpret_cast(functionPtr))) { + AbstractInfo = abstractInfo; + assert(functionPtr && "configuring callee without function pointer"); + // TODO: codegen asserts functionPtr is a pointer + // TODO: codegen asserts functionPtr is either an opaque pointer type or a + // pointer to a function + } + + static CIRGenCallee + forDirect(mlir::Operation *functionPtr, + const CIRGenCalleeInfo &abstractInfo = CIRGenCalleeInfo()) { + return CIRGenCallee(abstractInfo, functionPtr); + } + + bool isBuiltin() const { + return KindOrFunctionPointer == SpecialKind::Builtin; + } + + const clang::FunctionDecl *getBuiltinDecl() const { + assert(isBuiltin()); + return BuiltinInfo.Decl; + } + unsigned getBuiltinID() const { + assert(isBuiltin()); + return BuiltinInfo.ID; + } + + static CIRGenCallee forBuiltin(unsigned builtinID, + const clang::FunctionDecl *builtinDecl) { + CIRGenCallee result(SpecialKind::Builtin); + result.BuiltinInfo.Decl = builtinDecl; + result.BuiltinInfo.ID = builtinID; + return result; + } + + bool isPsuedoDestructor() const { + return KindOrFunctionPointer == SpecialKind::PsuedoDestructor; + } + + bool isOrdinary() const { + return uintptr_t(KindOrFunctionPointer) > uintptr_t(SpecialKind::Last); + } + + /// If this is a delayed callee computation of some sort, prepare a concrete + /// callee + CIRGenCallee prepareConcreteCallee(CIRGenFunction &CGF) const; + + mlir::Operation *getFunctionPointer() const { + assert(isOrdinary()); + return reinterpret_cast(KindOrFunctionPointer); + } + + CIRGenCalleeInfo getAbstractInfo() const { + if (isVirtual()) + return VirtualInfo.MD; + assert(isOrdinary()); + return AbstractInfo; + } + + bool isVirtual() const { + return KindOrFunctionPointer == SpecialKind::Virtual; + } + + static CIRGenCallee forVirtual(const clang::CallExpr *CE, + clang::GlobalDecl MD, Address Addr, + mlir::cir::FuncType FTy) { + CIRGenCallee result(SpecialKind::Virtual); + result.VirtualInfo.CE = CE; + result.VirtualInfo.MD = MD; + result.VirtualInfo.Addr = Addr; + result.VirtualInfo.FTy = FTy; + return result; + } + + const clang::CallExpr *getVirtualCallExpr() const { + assert(isVirtual()); + return VirtualInfo.CE; + } + + clang::GlobalDecl getVirtualMethodDecl() const { + assert(isVirtual()); + return VirtualInfo.MD; + } + Address getThisAddress() const { + assert(isVirtual()); + return VirtualInfo.Addr; + } + mlir::cir::FuncType getVirtualFunctionType() const { + assert(isVirtual()); + return VirtualInfo.FTy; + } + + void setFunctionPointer(mlir::Operation *functionPtr) { + assert(isOrdinary()); + KindOrFunctionPointer = + SpecialKind(reinterpret_cast(functionPtr)); + } +}; + +struct CallArg { +private: + union { + RValue RV; + LValue LV; /// This argument is semantically a load from this l-value + }; + bool HasLV; + + /// A data-flow flag to make sure getRValue and/or copyInto are not + /// called twice for duplicated IR emission. + mutable bool IsUsed; + +public: + clang::QualType Ty; + CallArg(RValue rv, clang::QualType ty) + : RV(rv), HasLV(false), IsUsed(false), Ty(ty) { + (void)IsUsed; + } + CallArg(LValue lv, clang::QualType ty) + : LV(lv), HasLV(true), IsUsed(false), Ty(ty) {} + + /// \returns an independent RValue. If the CallArg contains an LValue, + /// a temporary copy is returned. + RValue getRValue(CIRGenFunction &CGF, mlir::Location loc) const; + + bool hasLValue() const { return HasLV; } + + LValue getKnownLValue() const { + assert(HasLV && !IsUsed); + return LV; + } + + RValue getKnownRValue() const { + assert(!HasLV && !IsUsed); + return RV; + } + + bool isAggregate() const { return HasLV || RV.isAggregate(); } +}; + +class CallArgList : public llvm::SmallVector { +public: + CallArgList() {} + + struct Writeback { + LValue Source; + }; + + void add(RValue rvalue, clang::QualType type) { + push_back(CallArg(rvalue, type)); + } + + void addUncopiedAggregate(LValue LV, clang::QualType type) { + push_back(CallArg(LV, type)); + } + + /// Add all the arguments from another CallArgList to this one. After doing + /// this, the old CallArgList retains its list of arguments, but must not + /// be used to emit a call. + void addFrom(const CallArgList &other) { + insert(end(), other.begin(), other.end()); + // TODO: Writebacks, CleanupsToDeactivate, StackBase??? + } +}; + +/// Type for representing both the decl and type of parameters to a function. +/// The decl must be either a ParmVarDecl or ImplicitParamDecl. +class FunctionArgList : public llvm::SmallVector {}; + +/// Contains the address where the return value of a function can be stored, and +/// whether the address is volatile or not. +class ReturnValueSlot { + Address Addr = Address::invalid(); + + // Return value slot flags + unsigned IsVolatile : 1; + unsigned IsUnused : 1; + unsigned IsExternallyDestructed : 1; + +public: + ReturnValueSlot() + : IsVolatile(false), IsUnused(false), IsExternallyDestructed(false) {} + ReturnValueSlot(Address Addr, bool IsVolatile, bool IsUnused = false, + bool IsExternallyDestructed = false) + : Addr(Addr), IsVolatile(IsVolatile), IsUnused(IsUnused), + IsExternallyDestructed(IsExternallyDestructed) {} + + bool isNull() const { return !Addr.isValid(); } + bool isVolatile() const { return IsVolatile; } + Address getValue() const { return Addr; } + bool isUnused() const { return IsUnused; } + bool isExternallyDestructed() const { return IsExternallyDestructed; } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp new file mode 100644 index 000000000000..dbb23235220f --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -0,0 +1,1293 @@ +//===--- CIRGenClass.cpp - Emit CIR Code for C++ classes --------*- C++ -*-===// +// +// 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 dealing with C++ code generation of classes +// +//===----------------------------------------------------------------------===// + +#include "CIRGenCXXABI.h" +#include "CIRGenFunction.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/EvaluatedExprVisitor.h" +#include "clang/AST/RecordLayout.h" +#include "clang/Basic/NoSanitizeList.h" +#include "clang/Basic/TargetBuiltins.h" + +using namespace clang; +using namespace cir; + +/// Checks whether the given constructor is a valid subject for the +/// complete-to-base constructor delgation optimization, i.e. emitting the +/// complete constructor as a simple call to the base constructor. +bool CIRGenFunction::IsConstructorDelegationValid( + const CXXConstructorDecl *Ctor) { + + // Currently we disable the optimization for classes with virtual bases + // because (1) the address of parameter variables need to be consistent across + // all initializers but (2) the delegate function call necessarily creates a + // second copy of the parameter variable. + // + // The limiting example (purely theoretical AFAIK): + // struct A { A(int &c) { c++; } }; + // struct A : virtual A { + // B(int count) : A(count) { printf("%d\n", count); } + // }; + // ...although even this example could in principle be emitted as a delegation + // since the address of the parameter doesn't escape. + if (Ctor->getParent()->getNumVBases()) + return false; + + // We also disable the optimization for variadic functions because it's + // impossible to "re-pass" varargs. + if (Ctor->getType()->castAs()->isVariadic()) + return false; + + // FIXME: Decide if we can do a delegation of a delegating constructor. + if (Ctor->isDelegatingConstructor()) + llvm_unreachable("NYI"); + + return true; +} + +/// TODO(cir): strong candidate for AST helper to be shared between LLVM and CIR +/// codegen. +static bool isMemcpyEquivalentSpecialMember(const CXXMethodDecl *D) { + auto *CD = dyn_cast(D); + if (!(CD && CD->isCopyOrMoveConstructor()) && + !D->isCopyAssignmentOperator() && !D->isMoveAssignmentOperator()) + return false; + + // We can emit a memcpy for a trivial copy or move constructor/assignment. + if (D->isTrivial() && !D->getParent()->mayInsertExtraPadding()) + return true; + + // We *must* emit a memcpy for a defaulted union copy or move op. + if (D->getParent()->isUnion() && D->isDefaulted()) + return true; + + return false; +} + +namespace { +/// TODO(cir): a lot of what we see under this namespace is a strong candidate +/// to be shared between LLVM and CIR codegen. + +/// RAII object to indicate that codegen is copying the value representation +/// instead of the object representation. Useful when copying a struct or +/// class which has uninitialized members and we're only performing +/// lvalue-to-rvalue conversion on the object but not its members. +class CopyingValueRepresentation { +public: + explicit CopyingValueRepresentation(CIRGenFunction &CGF) + : CGF(CGF), OldSanOpts(CGF.SanOpts) { + CGF.SanOpts.set(SanitizerKind::Bool, false); + CGF.SanOpts.set(SanitizerKind::Enum, false); + } + ~CopyingValueRepresentation() { CGF.SanOpts = OldSanOpts; } + +private: + CIRGenFunction &CGF; + SanitizerSet OldSanOpts; +}; + +class FieldMemcpyizer { +public: + FieldMemcpyizer(CIRGenFunction &CGF, const CXXRecordDecl *ClassDecl, + const VarDecl *SrcRec) + : CGF(CGF), ClassDecl(ClassDecl), + // SrcRec(SrcRec), + RecLayout(CGF.getContext().getASTRecordLayout(ClassDecl)), + FirstField(nullptr), LastField(nullptr), FirstFieldOffset(0), + LastFieldOffset(0), LastAddedFieldIndex(0) { + (void)SrcRec; + } + + bool isMemcpyableField(FieldDecl *F) const { + // Never memcpy fields when we are adding poised paddings. + if (CGF.getContext().getLangOpts().SanitizeAddressFieldPadding) + return false; + Qualifiers Qual = F->getType().getQualifiers(); + if (Qual.hasVolatile() || Qual.hasObjCLifetime()) + return false; + + return true; + } + + void addMemcpyableField(FieldDecl *F) { + if (F->isZeroSize(CGF.getContext())) + return; + if (!FirstField) + addInitialField(F); + else + addNextField(F); + } + + CharUnits getMemcpySize(uint64_t FirstByteOffset) const { + ASTContext &Ctx = CGF.getContext(); + unsigned LastFieldSize = + LastField->isBitField() + ? LastField->getBitWidthValue(Ctx) + : Ctx.toBits( + Ctx.getTypeInfoDataSizeInChars(LastField->getType()).Width); + uint64_t MemcpySizeBits = LastFieldOffset + LastFieldSize - + FirstByteOffset + Ctx.getCharWidth() - 1; + CharUnits MemcpySize = Ctx.toCharUnitsFromBits(MemcpySizeBits); + return MemcpySize; + } + + void buildMemcpy() { + // Give the subclass a chance to bail out if it feels the memcpy isn't worth + // it (e.g. Hasn't aggregated enough data). + if (!FirstField) { + return; + } + + llvm_unreachable("NYI"); + } + + void reset() { FirstField = nullptr; } + +protected: + CIRGenFunction &CGF; + const CXXRecordDecl *ClassDecl; + +private: + void buildMemcpyIR(Address DestPtr, Address SrcPtr, CharUnits Size) { + llvm_unreachable("NYI"); + } + + void addInitialField(FieldDecl *F) { + FirstField = F; + LastField = F; + FirstFieldOffset = RecLayout.getFieldOffset(F->getFieldIndex()); + LastFieldOffset = FirstFieldOffset; + LastAddedFieldIndex = F->getFieldIndex(); + } + + void addNextField(FieldDecl *F) { + // For the most part, the following invariant will hold: + // F->getFieldIndex() == LastAddedFieldIndex + 1 + // The one exception is that Sema won't add a copy-initializer for an + // unnamed bitfield, which will show up here as a gap in the sequence. + assert(F->getFieldIndex() >= LastAddedFieldIndex + 1 && + "Cannot aggregate fields out of order."); + LastAddedFieldIndex = F->getFieldIndex(); + + // The 'first' and 'last' fields are chosen by offset, rather than field + // index. This allows the code to support bitfields, as well as regular + // fields. + uint64_t FOffset = RecLayout.getFieldOffset(F->getFieldIndex()); + if (FOffset < FirstFieldOffset) { + FirstField = F; + FirstFieldOffset = FOffset; + } else if (FOffset >= LastFieldOffset) { + LastField = F; + LastFieldOffset = FOffset; + } + } + + // const VarDecl *SrcRec; + const ASTRecordLayout &RecLayout; + FieldDecl *FirstField; + FieldDecl *LastField; + uint64_t FirstFieldOffset, LastFieldOffset; + unsigned LastAddedFieldIndex; +}; + +static void buildLValueForAnyFieldInitialization(CIRGenFunction &CGF, + CXXCtorInitializer *MemberInit, + LValue &LHS) { + FieldDecl *Field = MemberInit->getAnyMember(); + if (MemberInit->isIndirectMemberInitializer()) { + llvm_unreachable("NYI"); + } else { + LHS = CGF.buildLValueForFieldInitialization(LHS, Field, Field->getName()); + } +} + +static void buildMemberInitializer(CIRGenFunction &CGF, + const CXXRecordDecl *ClassDecl, + CXXCtorInitializer *MemberInit, + const CXXConstructorDecl *Constructor, + FunctionArgList &Args) { + // TODO: ApplyDebugLocation + assert(MemberInit->isAnyMemberInitializer() && + "Mush have member initializer!"); + assert(MemberInit->getInit() && "Must have initializer!"); + + // non-static data member initializers + FieldDecl *Field = MemberInit->getAnyMember(); + QualType FieldType = Field->getType(); + + auto ThisPtr = CGF.LoadCXXThis(); + QualType RecordTy = CGF.getContext().getTypeDeclType(ClassDecl); + LValue LHS; + + // If a base constructor is being emitted, create an LValue that has the + // non-virtual alignment. + if (CGF.CurGD.getCtorType() == Ctor_Base) + LHS = CGF.MakeNaturalAlignPointeeAddrLValue(ThisPtr, RecordTy); + else + LHS = CGF.MakeNaturalAlignAddrLValue(ThisPtr, RecordTy); + + buildLValueForAnyFieldInitialization(CGF, MemberInit, LHS); + + // Special case: If we are in a copy or move constructor, and we are copying + // an array off PODs or classes with tirival copy constructors, ignore the AST + // and perform the copy we know is equivalent. + // FIXME: This is hacky at best... if we had a bit more explicit information + // in the AST, we could generalize it more easily. + const ConstantArrayType *Array = + CGF.getContext().getAsConstantArrayType(FieldType); + if (Array && Constructor->isDefaulted() && + Constructor->isCopyOrMoveConstructor()) { + llvm_unreachable("NYI"); + } + + CGF.buildInitializerForField(Field, LHS, MemberInit->getInit()); +} + +class ConstructorMemcpyizer : public FieldMemcpyizer { +private: + /// Get source argument for copy constructor. Returns null if not a copy + /// constructor. + static const VarDecl *getTrivialCopySource(CIRGenFunction &CGF, + const CXXConstructorDecl *CD, + FunctionArgList &Args) { + if (CD->isCopyOrMoveConstructor() && CD->isDefaulted()) + return Args[CGF.CGM.getCXXABI().getSrcArgforCopyCtor(CD, Args)]; + + return nullptr; + } + + // Returns true if a CXXCtorInitializer represents a member initialization + // that can be rolled into a memcpy. + bool isMemberInitMemcpyable(CXXCtorInitializer *MemberInit) const { + if (!MemcpyableCtor) + return false; + + assert(!UnimplementedFeature::fieldMemcpyizerBuildMemcpy()); + return false; + } + +public: + ConstructorMemcpyizer(CIRGenFunction &CGF, const CXXConstructorDecl *CD, + FunctionArgList &Args) + : FieldMemcpyizer(CGF, CD->getParent(), + getTrivialCopySource(CGF, CD, Args)), + ConstructorDecl(CD), + MemcpyableCtor(CD->isDefaulted() && CD->isCopyOrMoveConstructor() && + CGF.getLangOpts().getGC() == LangOptions::NonGC), + Args(Args) {} + + void addMemberInitializer(CXXCtorInitializer *MemberInit) { + if (isMemberInitMemcpyable(MemberInit)) { + AggregatedInits.push_back(MemberInit); + addMemcpyableField(MemberInit->getMember()); + } else { + buildAggregatedInits(); + buildMemberInitializer(CGF, ConstructorDecl->getParent(), MemberInit, + ConstructorDecl, Args); + } + } + + void buildAggregatedInits() { + if (AggregatedInits.size() <= 1) { + // This memcpy is too small to be worthwhile. Fall back on default + // codegen. + if (!AggregatedInits.empty()) { + llvm_unreachable("NYI"); + } + reset(); + return; + } + + pushEHDestructors(); + buildMemcpy(); + AggregatedInits.clear(); + } + + void pushEHDestructors() { + Address ThisPtr = CGF.LoadCXXThisAddress(); + QualType RecordTy = CGF.getContext().getTypeDeclType(ClassDecl); + LValue LHS = CGF.makeAddrLValue(ThisPtr, RecordTy); + (void)LHS; + + for (unsigned i = 0; i < AggregatedInits.size(); ++i) { + CXXCtorInitializer *MemberInit = AggregatedInits[i]; + QualType FieldType = MemberInit->getAnyMember()->getType(); + QualType::DestructionKind dtorKind = FieldType.isDestructedType(); + if (!CGF.needsEHCleanup(dtorKind)) + continue; + LValue FieldLHS = LHS; + buildLValueForAnyFieldInitialization(CGF, MemberInit, FieldLHS); + CGF.pushEHDestroy(dtorKind, FieldLHS.getAddress(), FieldType); + } + } + + void finish() { buildAggregatedInits(); } + +private: + const CXXConstructorDecl *ConstructorDecl; + bool MemcpyableCtor; + FunctionArgList &Args; + SmallVector AggregatedInits; +}; + +class AssignmentMemcpyizer : public FieldMemcpyizer { +private: + // Returns the memcpyable field copied by the given statement, if one + // exists. Otherwise returns null. + FieldDecl *getMemcpyableField(Stmt *S) { + if (!AssignmentsMemcpyable) + return nullptr; + if (BinaryOperator *BO = dyn_cast(S)) { + // Recognise trivial assignments. + if (BO->getOpcode() != BO_Assign) + return nullptr; + MemberExpr *ME = dyn_cast(BO->getLHS()); + if (!ME) + return nullptr; + FieldDecl *Field = dyn_cast(ME->getMemberDecl()); + if (!Field || !isMemcpyableField(Field)) + return nullptr; + Stmt *RHS = BO->getRHS(); + if (ImplicitCastExpr *EC = dyn_cast(RHS)) + RHS = EC->getSubExpr(); + if (!RHS) + return nullptr; + if (MemberExpr *ME2 = dyn_cast(RHS)) { + if (ME2->getMemberDecl() == Field) + return Field; + } + return nullptr; + } else if (CXXMemberCallExpr *MCE = dyn_cast(S)) { + CXXMethodDecl *MD = dyn_cast(MCE->getCalleeDecl()); + if (!(MD && isMemcpyEquivalentSpecialMember(MD))) + return nullptr; + MemberExpr *IOA = dyn_cast(MCE->getImplicitObjectArgument()); + if (!IOA) + return nullptr; + FieldDecl *Field = dyn_cast(IOA->getMemberDecl()); + if (!Field || !isMemcpyableField(Field)) + return nullptr; + MemberExpr *Arg0 = dyn_cast(MCE->getArg(0)); + if (!Arg0 || Field != dyn_cast(Arg0->getMemberDecl())) + return nullptr; + return Field; + } else if (CallExpr *CE = dyn_cast(S)) { + FunctionDecl *FD = dyn_cast(CE->getCalleeDecl()); + if (!FD || FD->getBuiltinID() != Builtin::BI__builtin_memcpy) + return nullptr; + Expr *DstPtr = CE->getArg(0); + if (ImplicitCastExpr *DC = dyn_cast(DstPtr)) + DstPtr = DC->getSubExpr(); + UnaryOperator *DUO = dyn_cast(DstPtr); + if (!DUO || DUO->getOpcode() != UO_AddrOf) + return nullptr; + MemberExpr *ME = dyn_cast(DUO->getSubExpr()); + if (!ME) + return nullptr; + FieldDecl *Field = dyn_cast(ME->getMemberDecl()); + if (!Field || !isMemcpyableField(Field)) + return nullptr; + Expr *SrcPtr = CE->getArg(1); + if (ImplicitCastExpr *SC = dyn_cast(SrcPtr)) + SrcPtr = SC->getSubExpr(); + UnaryOperator *SUO = dyn_cast(SrcPtr); + if (!SUO || SUO->getOpcode() != UO_AddrOf) + return nullptr; + MemberExpr *ME2 = dyn_cast(SUO->getSubExpr()); + if (!ME2 || Field != dyn_cast(ME2->getMemberDecl())) + return nullptr; + return Field; + } + + return nullptr; + } + + bool AssignmentsMemcpyable; + SmallVector AggregatedStmts; + +public: + AssignmentMemcpyizer(CIRGenFunction &CGF, const CXXMethodDecl *AD, + FunctionArgList &Args) + : FieldMemcpyizer(CGF, AD->getParent(), Args[Args.size() - 1]), + AssignmentsMemcpyable(CGF.getLangOpts().getGC() == LangOptions::NonGC) { + assert(Args.size() == 2); + } + + void emitAssignment(Stmt *S) { + FieldDecl *F = getMemcpyableField(S); + if (F) { + addMemcpyableField(F); + AggregatedStmts.push_back(S); + } else { + emitAggregatedStmts(); + if (CGF.buildStmt(S, /*useCurrentScope=*/true).failed()) + llvm_unreachable("Should not get here!"); + } + } + + void emitAggregatedStmts() { + if (AggregatedStmts.size() <= 1) { + if (!AggregatedStmts.empty()) { + CopyingValueRepresentation CVR(CGF); + if (CGF.buildStmt(AggregatedStmts[0], /*useCurrentScope=*/true) + .failed()) + llvm_unreachable("Should not get here!"); + } + reset(); + } + + buildMemcpy(); + AggregatedStmts.clear(); + } + + void finish() { emitAggregatedStmts(); } +}; +} // namespace + +static bool isInitializerOfDynamicClass(const CXXCtorInitializer *BaseInit) { + const Type *BaseType = BaseInit->getBaseClass(); + const auto *BaseClassDecl = + cast(BaseType->castAs()->getDecl()); + return BaseClassDecl->isDynamicClass(); +} + +namespace { +/// Call the destructor for a direct base class. +struct CallBaseDtor final : EHScopeStack::Cleanup { + const CXXRecordDecl *BaseClass; + bool BaseIsVirtual; + CallBaseDtor(const CXXRecordDecl *Base, bool BaseIsVirtual) + : BaseClass(Base), BaseIsVirtual(BaseIsVirtual) {} + + void Emit(CIRGenFunction &CGF, Flags flags) override { + llvm_unreachable("NYI"); + } +}; + +/// A visitor which checks whether an initializer uses 'this' in a +/// way which requires the vtable to be properly set. +struct DynamicThisUseChecker + : ConstEvaluatedExprVisitor { + typedef ConstEvaluatedExprVisitor super; + + bool UsesThis; + + DynamicThisUseChecker(const ASTContext &C) : super(C), UsesThis(false) {} + + // Black-list all explicit and implicit references to 'this'. + // + // Do we need to worry about external references to 'this' derived + // from arbitrary code? If so, then anything which runs arbitrary + // external code might potentially access the vtable. + void VisitCXXThisExpr(const CXXThisExpr *E) { UsesThis = true; } +}; +} // end anonymous namespace + +static bool BaseInitializerUsesThis(ASTContext &C, const Expr *Init) { + DynamicThisUseChecker Checker(C); + Checker.Visit(Init); + return Checker.UsesThis; +} + +/// Gets the address of a direct base class within a complete object. +/// This should only be used for (1) non-virtual bases or (2) virtual bases +/// when the type is known to be complete (e.g. in complete destructors). +/// +/// The object pointed to by 'This' is assumed to be non-null. +Address CIRGenFunction::getAddressOfDirectBaseInCompleteClass( + mlir::Location loc, Address This, const CXXRecordDecl *Derived, + const CXXRecordDecl *Base, bool BaseIsVirtual) { + // 'this' must be a pointer (in some address space) to Derived. + assert(This.getElementType() == ConvertType(Derived)); + + // Compute the offset of the virtual base. + CharUnits Offset; + const ASTRecordLayout &Layout = getContext().getASTRecordLayout(Derived); + if (BaseIsVirtual) + Offset = Layout.getVBaseClassOffset(Base); + else + Offset = Layout.getBaseClassOffset(Base); + + // Shift and cast down to the base type. + // TODO: for complete types, this should be possible with a GEP. + Address V = This; + if (!Offset.isZero()) { + mlir::Value OffsetVal = builder.getSInt32(Offset.getQuantity(), loc); + mlir::Value VBaseThisPtr = builder.create( + loc, This.getPointer().getType(), This.getPointer(), OffsetVal); + V = Address(VBaseThisPtr, CXXABIThisAlignment); + } + V = builder.createElementBitCast(loc, V, ConvertType(Base)); + return V; +} + +static void buildBaseInitializer(mlir::Location loc, CIRGenFunction &CGF, + const CXXRecordDecl *ClassDecl, + CXXCtorInitializer *BaseInit) { + assert(BaseInit->isBaseInitializer() && "Must have base initializer!"); + + Address ThisPtr = CGF.LoadCXXThisAddress(); + + const Type *BaseType = BaseInit->getBaseClass(); + const auto *BaseClassDecl = + cast(BaseType->castAs()->getDecl()); + + bool isBaseVirtual = BaseInit->isBaseVirtual(); + + // If the initializer for the base (other than the constructor + // itself) accesses 'this' in any way, we need to initialize the + // vtables. + if (BaseInitializerUsesThis(CGF.getContext(), BaseInit->getInit())) + CGF.initializeVTablePointers(loc, ClassDecl); + + // We can pretend to be a complete class because it only matters for + // virtual bases, and we only do virtual bases for complete ctors. + Address V = CGF.getAddressOfDirectBaseInCompleteClass( + loc, ThisPtr, ClassDecl, BaseClassDecl, isBaseVirtual); + AggValueSlot AggSlot = AggValueSlot::forAddr( + V, Qualifiers(), AggValueSlot::IsDestructed, + AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased, + CGF.getOverlapForBaseInit(ClassDecl, BaseClassDecl, isBaseVirtual)); + + CGF.buildAggExpr(BaseInit->getInit(), AggSlot); + + if (CGF.CGM.getLangOpts().Exceptions && + !BaseClassDecl->hasTrivialDestructor()) + CGF.EHStack.pushCleanup(EHCleanup, BaseClassDecl, + isBaseVirtual); +} + +/// This routine generates necessary code to initialize base classes and +/// non-static data members belonging to this constructor. +void CIRGenFunction::buildCtorPrologue(const CXXConstructorDecl *CD, + CXXCtorType CtorType, + FunctionArgList &Args) { + if (CD->isDelegatingConstructor()) + llvm_unreachable("NYI"); + + const CXXRecordDecl *ClassDecl = CD->getParent(); + + CXXConstructorDecl::init_const_iterator B = CD->init_begin(), + E = CD->init_end(); + + // Virtual base initializers first, if any. They aren't needed if: + // - This is a base ctor variant + // - There are no vbases + // - The class is abstract, so a complete object of it cannot be constructed + // + // The check for an abstract class is necessary because sema may not have + // marked virtual base destructors referenced. + bool ConstructVBases = CtorType != Ctor_Base && + ClassDecl->getNumVBases() != 0 && + !ClassDecl->isAbstract(); + + // In the Microsoft C++ ABI, there are no constructor variants. Instead, the + // constructor of a class with virtual bases takes an additional parameter to + // conditionally construct the virtual bases. Emit that check here. + mlir::Block *BaseCtorContinueBB = nullptr; + if (ConstructVBases && + !CGM.getTarget().getCXXABI().hasConstructorVariants()) { + llvm_unreachable("NYI"); + } + + auto const OldThis = CXXThisValue; + for (; B != E && (*B)->isBaseInitializer() && (*B)->isBaseVirtual(); B++) { + if (!ConstructVBases) + continue; + if (CGM.getCodeGenOpts().StrictVTablePointers && + CGM.getCodeGenOpts().OptimizationLevel > 0 && + isInitializerOfDynamicClass(*B)) + llvm_unreachable("NYI"); + buildBaseInitializer(getLoc(CD->getBeginLoc()), *this, ClassDecl, *B); + } + + if (BaseCtorContinueBB) { + llvm_unreachable("NYI"); + } + + // Then, non-virtual base initializers. + for (; B != E && (*B)->isBaseInitializer(); B++) { + assert(!(*B)->isBaseVirtual()); + + if (CGM.getCodeGenOpts().StrictVTablePointers && + CGM.getCodeGenOpts().OptimizationLevel > 0 && + isInitializerOfDynamicClass(*B)) + llvm_unreachable("NYI"); + buildBaseInitializer(getLoc(CD->getBeginLoc()), *this, ClassDecl, *B); + } + + CXXThisValue = OldThis; + + initializeVTablePointers(getLoc(CD->getBeginLoc()), ClassDecl); + + // And finally, initialize class members. + FieldConstructionScope FCS(*this, LoadCXXThisAddress()); + ConstructorMemcpyizer CM(*this, CD, Args); + for (; B != E; B++) { + CXXCtorInitializer *Member = (*B); + assert(!Member->isBaseInitializer()); + assert(Member->isAnyMemberInitializer() && + "Delegating initializer on non-delegating constructor"); + CM.addMemberInitializer(Member); + } + CM.finish(); +} + +static Address ApplyNonVirtualAndVirtualOffset( + CIRGenFunction &CGF, Address addr, CharUnits nonVirtualOffset, + mlir::Value virtualOffset, const CXXRecordDecl *derivedClass, + const CXXRecordDecl *nearestVBase) { + llvm_unreachable("NYI"); + return Address::invalid(); +} + +void CIRGenFunction::initializeVTablePointer(mlir::Location loc, + const VPtr &Vptr) { + // Compute the address point. + auto VTableAddressPoint = CGM.getCXXABI().getVTableAddressPointInStructor( + *this, Vptr.VTableClass, Vptr.Base, Vptr.NearestVBase); + + if (!VTableAddressPoint) + return; + + // Compute where to store the address point. + mlir::Value VirtualOffset{}; + CharUnits NonVirtualOffset = CharUnits::Zero(); + + if (CGM.getCXXABI().isVirtualOffsetNeededForVTableField(*this, Vptr)) { + llvm_unreachable("NYI"); + } else { + // We can just use the base offset in the complete class. + NonVirtualOffset = Vptr.Base.getBaseOffset(); + } + + // Apply the offsets. + Address VTableField = LoadCXXThisAddress(); + if (!NonVirtualOffset.isZero() || VirtualOffset) { + VTableField = ApplyNonVirtualAndVirtualOffset( + *this, VTableField, NonVirtualOffset, VirtualOffset, Vptr.VTableClass, + Vptr.NearestVBase); + } + + // Finally, store the address point. Use the same CIR types as the field. + // + // vtable field is derived from `this` pointer, therefore they should be in + // the same addr space. + assert(!UnimplementedFeature::addressSpace()); + VTableField = builder.createElementBitCast(loc, VTableField, + VTableAddressPoint.getType()); + builder.createStore(loc, VTableAddressPoint, VTableField); + assert(!UnimplementedFeature::tbaa()); +} + +void CIRGenFunction::initializeVTablePointers(mlir::Location loc, + const CXXRecordDecl *RD) { + // Ignore classes without a vtable. + if (!RD->isDynamicClass()) + return; + + // Initialize the vtable pointers for this class and all of its bases. + if (CGM.getCXXABI().doStructorsInitializeVPtrs(RD)) + for (const auto &Vptr : getVTablePointers(RD)) + initializeVTablePointer(loc, Vptr); + + if (RD->getNumVBases()) + CGM.getCXXABI().initializeHiddenVirtualInheritanceMembers(*this, RD); +} + +CIRGenFunction::VPtrsVector +CIRGenFunction::getVTablePointers(const CXXRecordDecl *VTableClass) { + CIRGenFunction::VPtrsVector VPtrsResult; + VisitedVirtualBasesSetTy VBases; + getVTablePointers(BaseSubobject(VTableClass, CharUnits::Zero()), + /*NearestVBase=*/nullptr, + /*OffsetFromNearestVBase=*/CharUnits::Zero(), + /*BaseIsNonVirtualPrimaryBase=*/false, VTableClass, VBases, + VPtrsResult); + return VPtrsResult; +} + +void CIRGenFunction::getVTablePointers(BaseSubobject Base, + const CXXRecordDecl *NearestVBase, + CharUnits OffsetFromNearestVBase, + bool BaseIsNonVirtualPrimaryBase, + const CXXRecordDecl *VTableClass, + VisitedVirtualBasesSetTy &VBases, + VPtrsVector &Vptrs) { + // If this base is a non-virtual primary base the address point has already + // been set. + if (!BaseIsNonVirtualPrimaryBase) { + // Initialize the vtable pointer for this base. + VPtr Vptr = {Base, NearestVBase, OffsetFromNearestVBase, VTableClass}; + Vptrs.push_back(Vptr); + } + + const CXXRecordDecl *RD = Base.getBase(); + + // Traverse bases. + for (const auto &I : RD->bases()) { + auto *BaseDecl = + cast(I.getType()->castAs()->getDecl()); + + // Ignore classes without a vtable. + if (!BaseDecl->isDynamicClass()) + continue; + + CharUnits BaseOffset; + CharUnits BaseOffsetFromNearestVBase; + bool BaseDeclIsNonVirtualPrimaryBase; + + if (I.isVirtual()) { + llvm_unreachable("NYI"); + } else { + const ASTRecordLayout &Layout = getContext().getASTRecordLayout(RD); + + BaseOffset = Base.getBaseOffset() + Layout.getBaseClassOffset(BaseDecl); + BaseOffsetFromNearestVBase = + OffsetFromNearestVBase + Layout.getBaseClassOffset(BaseDecl); + BaseDeclIsNonVirtualPrimaryBase = Layout.getPrimaryBase() == BaseDecl; + } + + getVTablePointers( + BaseSubobject(BaseDecl, BaseOffset), + I.isVirtual() ? BaseDecl : NearestVBase, BaseOffsetFromNearestVBase, + BaseDeclIsNonVirtualPrimaryBase, VTableClass, VBases, Vptrs); + } +} + +Address CIRGenFunction::LoadCXXThisAddress() { + assert(CurFuncDecl && "loading 'this' without a func declaration?"); + assert(isa(CurFuncDecl)); + + // Lazily compute CXXThisAlignment. + if (CXXThisAlignment.isZero()) { + // Just use the best known alignment for the parent. + // TODO: if we're currently emitting a complete-object ctor/dtor, we can + // always use the complete-object alignment. + auto RD = cast(CurFuncDecl)->getParent(); + CXXThisAlignment = CGM.getClassPointerAlignment(RD); + } + + return Address(LoadCXXThis(), CXXThisAlignment); +} + +void CIRGenFunction::buildInitializerForField(FieldDecl *Field, LValue LHS, + Expr *Init) { + QualType FieldType = Field->getType(); + switch (getEvaluationKind(FieldType)) { + case TEK_Scalar: + if (LHS.isSimple()) { + buildExprAsInit(Init, Field, LHS, false); + } else { + llvm_unreachable("NYI"); + } + break; + case TEK_Complex: + llvm_unreachable("NYI"); + break; + case TEK_Aggregate: { + AggValueSlot Slot = AggValueSlot::forLValue( + LHS, AggValueSlot::IsDestructed, AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased, getOverlapForFieldInit(Field), + AggValueSlot::IsNotZeroed, + // Checks are made by the code that calls constructor. + AggValueSlot::IsSanitizerChecked); + buildAggExpr(Init, Slot); + break; + } + } + + // Ensure that we destroy this object if an exception is thrown later in the + // constructor. + QualType::DestructionKind dtorKind = FieldType.isDestructedType(); + (void)dtorKind; + if (UnimplementedFeature::cleanups()) + llvm_unreachable("NYI"); +} + +void CIRGenFunction::buildDelegateCXXConstructorCall( + const CXXConstructorDecl *Ctor, CXXCtorType CtorType, + const FunctionArgList &Args, SourceLocation Loc) { + CallArgList DelegateArgs; + + FunctionArgList::const_iterator I = Args.begin(), E = Args.end(); + assert(I != E && "no parameters to constructor"); + + // this + Address This = LoadCXXThisAddress(); + DelegateArgs.add(RValue::get(This.getPointer()), (*I)->getType()); + ++I; + + // FIXME: The location of the VTT parameter in the parameter list is specific + // to the Itanium ABI and shouldn't be hardcoded here. + if (CGM.getCXXABI().NeedsVTTParameter(CurGD)) { + llvm_unreachable("NYI"); + } + + // Explicit arguments. + for (; I != E; ++I) { + const VarDecl *param = *I; + // FIXME: per-argument source location + buildDelegateCallArg(DelegateArgs, param, Loc); + } + + buildCXXConstructorCall(Ctor, CtorType, /*ForVirtualBase=*/false, + /*Delegating=*/true, This, DelegateArgs, + AggValueSlot::MayOverlap, Loc, + /*NewPointerIsChecked=*/true); +} + +void CIRGenFunction::buildImplicitAssignmentOperatorBody( + FunctionArgList &Args) { + const CXXMethodDecl *AssignOp = cast(CurGD.getDecl()); + const Stmt *RootS = AssignOp->getBody(); + assert(isa(RootS) && + "Body of an implicit assignment operator should be compound stmt."); + const CompoundStmt *RootCS = cast(RootS); + + // LexicalScope Scope(*this, RootCS->getSourceRange()); + // FIXME(cir): add all of the below under a new scope. + + assert(!UnimplementedFeature::incrementProfileCounter()); + AssignmentMemcpyizer AM(*this, AssignOp, Args); + for (auto *I : RootCS->body()) + AM.emitAssignment(I); + AM.finish(); +} + +void CIRGenFunction::buildForwardingCallToLambda( + const CXXMethodDecl *callOperator, CallArgList &callArgs) { + // Get the address of the call operator. + const auto &calleeFnInfo = + CGM.getTypes().arrangeCXXMethodDeclaration(callOperator); + auto calleePtr = CGM.GetAddrOfFunction( + GlobalDecl(callOperator), CGM.getTypes().GetFunctionType(calleeFnInfo)); + + // Prepare the return slot. + const FunctionProtoType *FPT = + callOperator->getType()->castAs(); + QualType resultType = FPT->getReturnType(); + ReturnValueSlot returnSlot; + if (!resultType->isVoidType() && + calleeFnInfo.getReturnInfo().getKind() == ABIArgInfo::Indirect && + !hasScalarEvaluationKind(calleeFnInfo.getReturnType())) { + llvm_unreachable("NYI"); + } + + // We don't need to separately arrange the call arguments because + // the call can't be variadic anyway --- it's impossible to forward + // variadic arguments. + + // Now emit our call. + auto callee = CIRGenCallee::forDirect(calleePtr, GlobalDecl(callOperator)); + RValue RV = buildCall(calleeFnInfo, callee, returnSlot, callArgs); + + // If necessary, copy the returned value into the slot. + if (!resultType->isVoidType() && returnSlot.isNull()) { + if (getLangOpts().ObjCAutoRefCount && resultType->isObjCRetainableType()) + llvm_unreachable("NYI"); + buildReturnOfRValue(*currSrcLoc, RV, resultType); + } else { + llvm_unreachable("NYI"); + } +} + +void CIRGenFunction::buildLambdaDelegatingInvokeBody(const CXXMethodDecl *MD) { + const CXXRecordDecl *Lambda = MD->getParent(); + + // Start building arguments for forwarding call + CallArgList CallArgs; + + QualType LambdaType = getContext().getRecordType(Lambda); + QualType ThisType = getContext().getPointerType(LambdaType); + Address ThisPtr = + CreateMemTemp(LambdaType, getLoc(MD->getSourceRange()), "unused.capture"); + CallArgs.add(RValue::get(ThisPtr.getPointer()), ThisType); + + // Add the rest of the parameters. + for (auto *Param : MD->parameters()) + buildDelegateCallArg(CallArgs, Param, Param->getBeginLoc()); + + const CXXMethodDecl *CallOp = Lambda->getLambdaCallOperator(); + // For a generic lambda, find the corresponding call operator specialization + // to which the call to the static-invoker shall be forwarded. + if (Lambda->isGenericLambda()) { + assert(MD->isFunctionTemplateSpecialization()); + const TemplateArgumentList *TAL = MD->getTemplateSpecializationArgs(); + FunctionTemplateDecl *CallOpTemplate = + CallOp->getDescribedFunctionTemplate(); + void *InsertPos = nullptr; + FunctionDecl *CorrespondingCallOpSpecialization = + CallOpTemplate->findSpecialization(TAL->asArray(), InsertPos); + assert(CorrespondingCallOpSpecialization); + CallOp = cast(CorrespondingCallOpSpecialization); + } + buildForwardingCallToLambda(CallOp, CallArgs); +} + +void CIRGenFunction::buildLambdaStaticInvokeBody(const CXXMethodDecl *MD) { + if (MD->isVariadic()) { + // Codgen for LLVM doesn't emit code for this as well, it says: + // FIXME: Making this work correctly is nasty because it requires either + // cloning the body of the call operator or making the call operator + // forward. + llvm_unreachable("NYI"); + } + + buildLambdaDelegatingInvokeBody(MD); +} + +void CIRGenFunction::destroyCXXObject(CIRGenFunction &CGF, Address addr, + QualType type) { + const RecordType *rtype = type->castAs(); + const CXXRecordDecl *record = cast(rtype->getDecl()); + const CXXDestructorDecl *dtor = record->getDestructor(); + assert(!dtor->isTrivial()); + llvm_unreachable("NYI"); + // CGF.buildCXXDestructorCall(dtor, Dtor_Complete, /*for vbase*/ false, + // /*Delegating=*/false, addr, type); +} + +/// Emits the body of the current destructor. +void CIRGenFunction::buildDestructorBody(FunctionArgList &Args) { + const CXXDestructorDecl *Dtor = cast(CurGD.getDecl()); + CXXDtorType DtorType = CurGD.getDtorType(); + + // For an abstract class, non-base destructors are never used (and can't + // be emitted in general, because vbase dtors may not have been validated + // by Sema), but the Itanium ABI doesn't make them optional and Clang may + // in fact emit references to them from other compilations, so emit them + // as functions containing a trap instruction. + if (DtorType != Dtor_Base && Dtor->getParent()->isAbstract()) { + llvm_unreachable("NYI"); + } + + Stmt *Body = Dtor->getBody(); + if (Body) + assert(!UnimplementedFeature::incrementProfileCounter()); + + // The call to operator delete in a deleting destructor happens + // outside of the function-try-block, which means it's always + // possible to delegate the destructor body to the complete + // destructor. Do so. + if (DtorType == Dtor_Deleting) { + RunCleanupsScope DtorEpilogue(*this); + EnterDtorCleanups(Dtor, Dtor_Deleting); + if (HaveInsertPoint()) { + QualType ThisTy = Dtor->getThisObjectType(); + buildCXXDestructorCall(Dtor, Dtor_Complete, /*ForVirtualBase=*/false, + /*Delegating=*/false, LoadCXXThisAddress(), + ThisTy); + } + return; + } + + // If the body is a function-try-block, enter the try before + // anything else. + bool isTryBody = (Body && isa(Body)); + if (isTryBody) { + llvm_unreachable("NYI"); + // EnterCXXTryStmt(*cast(Body), true); + } + assert(!UnimplementedFeature::emitAsanPrologueOrEpilogue()); + + // Enter the epilogue cleanups. + llvm_unreachable("NYI"); + // RunCleanupsScope DtorEpilogue(*this); + + // If this is the complete variant, just invoke the base variant; + // the epilogue will destruct the virtual bases. But we can't do + // this optimization if the body is a function-try-block, because + // we'd introduce *two* handler blocks. In the Microsoft ABI, we + // always delegate because we might not have a definition in this TU. + switch (DtorType) { + case Dtor_Comdat: + llvm_unreachable("not expecting a COMDAT"); + case Dtor_Deleting: + llvm_unreachable("already handled deleting case"); + + case Dtor_Complete: + llvm_unreachable("NYI"); + // assert((Body || getTarget().getCXXABI().isMicrosoft()) && + // "can't emit a dtor without a body for non-Microsoft ABIs"); + + // // Enter the cleanup scopes for virtual bases. + // EnterDtorCleanups(Dtor, Dtor_Complete); + + // if (!isTryBody) { + // QualType ThisTy = Dtor->getThisObjectType(); + // EmitCXXDestructorCall(Dtor, Dtor_Base, /*ForVirtualBase=*/false, + // /*Delegating=*/false, LoadCXXThisAddress(), + // ThisTy); + // break; + // } + + // Fallthrough: act like we're in the base variant. + [[fallthrough]]; + + case Dtor_Base: + llvm_unreachable("NYI"); + assert(Body); + + // // Enter the cleanup scopes for fields and non-virtual bases. + // EnterDtorCleanups(Dtor, Dtor_Base); + + // // Initialize the vtable pointers before entering the body. + // if (!CanSkipVTablePointerInitialization(*this, Dtor)) { + // // Insert the llvm.launder.invariant.group intrinsic before + // initializing + // // the vptrs to cancel any previous assumptions we might have made. + // if (CGM.getCodeGenOpts().StrictVTablePointers && + // CGM.getCodeGenOpts().OptimizationLevel > 0) + // CXXThisValue = Builder.CreateLaunderInvariantGroup(LoadCXXThis()); + // InitializeVTablePointers(Dtor->getParent()); + // } + + // if (isTryBody) + // EmitStmt(cast(Body)->getTryBlock()); + // else if (Body) + // EmitStmt(Body); + // else { + // assert(Dtor->isImplicit() && "bodyless dtor not implicit"); + // // nothing to do besides what's in the epilogue + // } + // // -fapple-kext must inline any call to this dtor into + // // the caller's body. + // if (getLangOpts().AppleKext) + // CurFn->addFnAttr(llvm::Attribute::AlwaysInline); + + // break; + } + + // Jump out through the epilogue cleanups. + llvm_unreachable("NYI"); + // DtorEpilogue.ForceCleanup(); + + // Exit the try if applicable. + if (isTryBody) + llvm_unreachable("NYI"); +} + +namespace { +[[maybe_unused]] mlir::Value +LoadThisForDtorDelete(CIRGenFunction &CGF, const CXXDestructorDecl *DD) { + if (Expr *ThisArg = DD->getOperatorDeleteThisArg()) + return CGF.buildScalarExpr(ThisArg); + return CGF.LoadCXXThis(); +} + +/// Call the operator delete associated with the current destructor. +struct CallDtorDelete final : EHScopeStack::Cleanup { + CallDtorDelete() {} + + void Emit(CIRGenFunction &CGF, Flags flags) override { + const CXXDestructorDecl *Dtor = cast(CGF.CurCodeDecl); + const CXXRecordDecl *ClassDecl = Dtor->getParent(); + CGF.buildDeleteCall(Dtor->getOperatorDelete(), + LoadThisForDtorDelete(CGF, Dtor), + CGF.getContext().getTagDeclType(ClassDecl)); + } +}; +} // namespace + +/// Emit all code that comes at the end of class's destructor. This is to call +/// destructors on members and base classes in reverse order of their +/// construction. +/// +/// For a deleting destructor, this also handles the case where a destroying +/// operator delete completely overrides the definition. +void CIRGenFunction::EnterDtorCleanups(const CXXDestructorDecl *DD, + CXXDtorType DtorType) { + assert((!DD->isTrivial() || DD->hasAttr()) && + "Should not emit dtor epilogue for non-exported trivial dtor!"); + + // The deleting-destructor phase just needs to call the appropriate + // operator delete that Sema picked up. + if (DtorType == Dtor_Deleting) { + assert(DD->getOperatorDelete() && + "operator delete missing - EnterDtorCleanups"); + if (CXXStructorImplicitParamValue) { + llvm_unreachable("NYI"); + } else { + if (DD->getOperatorDelete()->isDestroyingOperatorDelete()) { + llvm_unreachable("NYI"); + } else { + EHStack.pushCleanup(NormalAndEHCleanup); + } + } + return; + } + + llvm_unreachable("NYI"); +} + +void CIRGenFunction::buildCXXDestructorCall(const CXXDestructorDecl *DD, + CXXDtorType Type, + bool ForVirtualBase, + bool Delegating, Address This, + QualType ThisTy) { + CGM.getCXXABI().buildDestructorCall(*this, DD, Type, ForVirtualBase, + Delegating, This, ThisTy); +} + +mlir::Value CIRGenFunction::GetVTTParameter(GlobalDecl GD, bool ForVirtualBase, + bool Delegating) { + if (!CGM.getCXXABI().NeedsVTTParameter(GD)) { + // This constructor/destructor does not need a VTT parameter. + return nullptr; + } + + const CXXRecordDecl *RD = cast(CurCodeDecl)->getParent(); + const CXXRecordDecl *Base = cast(GD.getDecl())->getParent(); + + if (Delegating) { + llvm_unreachable("NYI"); + } else if (RD == Base) { + llvm_unreachable("NYI"); + } else { + llvm_unreachable("NYI"); + } + + if (CGM.getCXXABI().NeedsVTTParameter(CurGD)) { + llvm_unreachable("NYI"); + } else { + llvm_unreachable("NYI"); + } +} + +Address +CIRGenFunction::getAddressOfBaseClass(Address Value, + const CXXRecordDecl *Derived, + CastExpr::path_const_iterator PathBegin, + CastExpr::path_const_iterator PathEnd, + bool NullCheckValue, SourceLocation Loc) { + assert(PathBegin != PathEnd && "Base path should not be empty!"); + + CastExpr::path_const_iterator Start = PathBegin; + const CXXRecordDecl *VBase = nullptr; + + // Sema has done some convenient canonicalization here: if the + // access path involved any virtual steps, the conversion path will + // *start* with a step down to the correct virtual base subobject, + // and hence will not require any further steps. + if ((*Start)->isVirtual()) { + llvm_unreachable("NYI"); + } + + // Compute the static offset of the ultimate destination within its + // allocating subobject (the virtual base, if there is one, or else + // the "complete" object that we see). + CharUnits NonVirtualOffset = CGM.computeNonVirtualBaseClassOffset( + VBase ? VBase : Derived, Start, PathEnd); + + // If there's a virtual step, we can sometimes "devirtualize" it. + // For now, that's limited to when the derived type is final. + // TODO: "devirtualize" this for accesses to known-complete objects. + if (VBase && Derived->hasAttr()) { + llvm_unreachable("NYI"); + } + + // Get the base pointer type. + auto BaseValueTy = convertType((PathEnd[-1])->getType()); + assert(!UnimplementedFeature::addressSpace()); + // auto BasePtrTy = builder.getPointerTo(BaseValueTy); + // QualType DerivedTy = getContext().getRecordType(Derived); + // CharUnits DerivedAlign = CGM.getClassPointerAlignment(Derived); + + // If the static offset is zero and we don't have a virtual step, + // just do a bitcast; null checks are unnecessary. + if (NonVirtualOffset.isZero() && !VBase) { + if (sanitizePerformTypeCheck()) { + llvm_unreachable("NYI"); + } + return builder.createBaseClassAddr(getLoc(Loc), Value, BaseValueTy); + } + + // Skip over the offset (and the vtable load) if we're supposed to + // null-check the pointer. + if (NullCheckValue) { + llvm_unreachable("NYI"); + } + + if (sanitizePerformTypeCheck()) { + llvm_unreachable("NYI"); + } + + // Compute the virtual offset. + mlir::Value VirtualOffset{}; + if (VBase) { + llvm_unreachable("NYI"); + } + + // Apply both offsets. + Value = ApplyNonVirtualAndVirtualOffset(*this, Value, NonVirtualOffset, + VirtualOffset, Derived, VBase); + // Cast to the destination type. + Value = builder.createElementBitCast(Value.getPointer().getLoc(), Value, + BaseValueTy); + + // Build a phi if we needed a null check. + if (NullCheckValue) { + llvm_unreachable("NYI"); + } + + llvm_unreachable("NYI"); + return Value; +} + +// TODO(cir): this can be shared with LLVM codegen. +bool CIRGenFunction::shouldEmitVTableTypeCheckedLoad(const CXXRecordDecl *RD) { + if (!CGM.getCodeGenOpts().WholeProgramVTables || + !CGM.HasHiddenLTOVisibility(RD)) + return false; + + if (CGM.getCodeGenOpts().VirtualFunctionElimination) + return true; + + if (!SanOpts.has(SanitizerKind::CFIVCall) || + !CGM.getCodeGenOpts().SanitizeTrap.has(SanitizerKind::CFIVCall)) + return false; + + std::string TypeName = RD->getQualifiedNameAsString(); + return !getContext().getNoSanitizeList().containsType(SanitizerKind::CFIVCall, + TypeName); +} + +void CIRGenFunction::buildTypeMetadataCodeForVCall(const CXXRecordDecl *RD, + mlir::Value VTable, + SourceLocation Loc) { + if (SanOpts.has(SanitizerKind::CFIVCall)) { + llvm_unreachable("NYI"); + } else if (CGM.getCodeGenOpts().WholeProgramVTables && + // Don't insert type test assumes if we are forcing public + // visibility. + !CGM.AlwaysHasLTOVisibilityPublic(RD)) { + llvm_unreachable("NYI"); + } +} + +mlir::Value CIRGenFunction::getVTablePtr(SourceLocation Loc, Address This, + mlir::Type VTableTy, + const CXXRecordDecl *RD) { + auto loc = getLoc(Loc); + Address VTablePtrSrc = builder.createElementBitCast(loc, This, VTableTy); + auto VTable = builder.createLoad(loc, VTablePtrSrc); + assert(!UnimplementedFeature::tbaa()); + + if (CGM.getCodeGenOpts().OptimizationLevel > 0 && + CGM.getCodeGenOpts().StrictVTablePointers) { + assert(!UnimplementedFeature::createInvariantGroup()); + } + + return VTable; +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp new file mode 100644 index 000000000000..6350e8ddc118 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.cpp @@ -0,0 +1,471 @@ +//===--- CIRGenCleanup.cpp - Bookkeeping and code emission for cleanups ---===// +// +// 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 contains code dealing with the IR generation for cleanups +// and related information. +// +// A "cleanup" is a piece of code which needs to be executed whenever +// control transfers out of a particular scope. This can be +// conditionalized to occur only on exceptional control flow, only on +// normal control flow, or both. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenCleanup.h" +#include "CIRGenFunction.h" + +using namespace cir; +using namespace clang; +using namespace mlir::cir; + +//===----------------------------------------------------------------------===// +// CIRGenFunction cleanup related +//===----------------------------------------------------------------------===// + +/// Build a unconditional branch to the lexical scope cleanup block +/// or with the labeled blocked if already solved. +/// +/// Track on scope basis, goto's we need to fix later. +mlir::cir::BrOp CIRGenFunction::buildBranchThroughCleanup(mlir::Location Loc, + JumpDest Dest) { + // Remove this once we go for making sure unreachable code is + // well modeled (or not). + assert(builder.getInsertionBlock() && "not yet implemented"); + assert(!UnimplementedFeature::ehStack()); + + // Insert a branch: to the cleanup block (unsolved) or to the already + // materialized label. Keep track of unsolved goto's. + return builder.create(Loc, Dest.isValid() ? Dest.getBlock() + : ReturnBlock().getBlock()); +} + +/// Emits all the code to cause the given temporary to be cleaned up. +void CIRGenFunction::buildCXXTemporary(const CXXTemporary *Temporary, + QualType TempType, Address Ptr) { + pushDestroy(NormalAndEHCleanup, Ptr, TempType, destroyCXXObject, + /*useEHCleanup*/ true); +} + +Address CIRGenFunction::createCleanupActiveFlag() { llvm_unreachable("NYI"); } + +DominatingValue::saved_type +DominatingValue::saved_type::save(CIRGenFunction &CGF, RValue rv) { + llvm_unreachable("NYI"); +} + +/// Deactive a cleanup that was created in an active state. +void CIRGenFunction::DeactivateCleanupBlock(EHScopeStack::stable_iterator C, + mlir::Operation *dominatingIP) { + assert(C != EHStack.stable_end() && "deactivating bottom of stack?"); + EHCleanupScope &Scope = cast(*EHStack.find(C)); + assert(Scope.isActive() && "double deactivation"); + + // If it's the top of the stack, just pop it, but do so only if it belongs + // to the current RunCleanupsScope. + if (C == EHStack.stable_begin() && + CurrentCleanupScopeDepth.strictlyEncloses(C)) { + // Per comment below, checking EHAsynch is not really necessary + // it's there to assure zero-impact w/o EHAsynch option + if (!Scope.isNormalCleanup() && getLangOpts().EHAsynch) { + llvm_unreachable("NYI"); + } else { + // From LLVM: If it's a normal cleanup, we need to pretend that the + // fallthrough is unreachable. + // CIR remarks: LLVM uses an empty insertion point to signal behavior + // change to other codegen paths (triggered by PopCleanupBlock). + // CIRGen doesn't do that yet, but let's mimic just in case. + mlir::OpBuilder::InsertionGuard guard(builder); + builder.clearInsertionPoint(); + PopCleanupBlock(); + } + return; + } + + llvm_unreachable("NYI"); +} + +void CIRGenFunction::initFullExprCleanupWithFlag(Address ActiveFlag) { + // Set that as the active flag in the cleanup. + EHCleanupScope &cleanup = cast(*EHStack.begin()); + assert(!cleanup.hasActiveFlag() && "cleanup already has active flag?"); + cleanup.setActiveFlag(ActiveFlag); + + if (cleanup.isNormalCleanup()) + cleanup.setTestFlagInNormalCleanup(); + if (cleanup.isEHCleanup()) + cleanup.setTestFlagInEHCleanup(); +} + +/// We don't need a normal entry block for the given cleanup. +/// Optimistic fixup branches can cause these blocks to come into +/// existence anyway; if so, destroy it. +/// +/// The validity of this transformation is very much specific to the +/// exact ways in which we form branches to cleanup entries. +static void destroyOptimisticNormalEntry(CIRGenFunction &CGF, + EHCleanupScope &scope) { + auto *entry = scope.getNormalBlock(); + if (!entry) + return; + + llvm_unreachable("NYI"); +} + +static void buildCleanup(CIRGenFunction &CGF, EHScopeStack::Cleanup *Fn, + EHScopeStack::Cleanup::Flags flags, + Address ActiveFlag) { + // If there's an active flag, load it and skip the cleanup if it's + // false. + if (ActiveFlag.isValid()) { + llvm_unreachable("NYI"); + } + + // Ask the cleanup to emit itself. + Fn->Emit(CGF, flags); + assert(CGF.HaveInsertPoint() && "cleanup ended with no insertion point?"); + + // Emit the continuation block if there was an active flag. + if (ActiveFlag.isValid()) { + llvm_unreachable("NYI"); + } +} + +/// Pops a cleanup block. If the block includes a normal cleanup, the +/// current insertion point is threaded through the cleanup, as are +/// any branch fixups on the cleanup. +void CIRGenFunction::PopCleanupBlock(bool FallthroughIsBranchThrough) { + assert(!EHStack.empty() && "cleanup stack is empty!"); + assert(isa(*EHStack.begin()) && "top not a cleanup!"); + [[maybe_unused]] EHCleanupScope &Scope = + cast(*EHStack.begin()); + assert(Scope.getFixupDepth() <= EHStack.getNumBranchFixups()); + + // Remember activation information. + [[maybe_unused]] bool IsActive = Scope.isActive(); + [[maybe_unused]] Address NormalActiveFlag = + Scope.shouldTestFlagInNormalCleanup() ? Scope.getActiveFlag() + : Address::invalid(); + [[maybe_unused]] Address EHActiveFlag = Scope.shouldTestFlagInEHCleanup() + ? Scope.getActiveFlag() + : Address::invalid(); + + // Check whether we need an EH cleanup. This is only true if we've + // generated a lazy EH cleanup block. + auto *EHEntry = Scope.getCachedEHDispatchBlock(); + assert(Scope.hasEHBranches() == (EHEntry != nullptr)); + bool RequiresEHCleanup = (EHEntry != nullptr); + + // Check the three conditions which might require a normal cleanup: + + // - whether there are branch fix-ups through this cleanup + unsigned FixupDepth = Scope.getFixupDepth(); + bool HasFixups = EHStack.getNumBranchFixups() != FixupDepth; + + // - whether there are branch-throughs or branch-afters + bool HasExistingBranches = Scope.hasBranches(); + + // - whether there's a fallthrough + auto *FallthroughSource = builder.getInsertionBlock(); + bool HasFallthrough = (FallthroughSource != nullptr && IsActive); + + // Branch-through fall-throughs leave the insertion point set to the + // end of the last cleanup, which points to the current scope. The + // rest of CIR gen doesn't need to worry about this; it only happens + // during the execution of PopCleanupBlocks(). + bool HasTerminator = + FallthroughSource && !FallthroughSource->empty() && + FallthroughSource->back().mightHaveTrait(); + bool HasPrebranchedFallthrough = (FallthroughSource && HasTerminator && + FallthroughSource->getTerminator()); + + // If this is a normal cleanup, then having a prebranched + // fallthrough implies that the fallthrough source unconditionally + // jumps here. + assert(!Scope.isNormalCleanup() || !HasPrebranchedFallthrough || + (Scope.getNormalBlock() && + FallthroughSource->getTerminator()->getSuccessor(0) == + Scope.getNormalBlock())); + + bool RequiresNormalCleanup = false; + if (Scope.isNormalCleanup() && + (HasFixups || HasExistingBranches || HasFallthrough)) { + RequiresNormalCleanup = true; + } + + // If we have a prebranched fallthrough into an inactive normal + // cleanup, rewrite it so that it leads to the appropriate place. + if (Scope.isNormalCleanup() && HasPrebranchedFallthrough && !IsActive) { + llvm_unreachable("NYI"); + } + + // If we don't need the cleanup at all, we're done. + if (!RequiresNormalCleanup && !RequiresEHCleanup) { + destroyOptimisticNormalEntry(*this, Scope); + EHStack.popCleanup(); // safe because there are no fixups + assert(EHStack.getNumBranchFixups() == 0 || EHStack.hasNormalCleanups()); + return; + } + + // Copy the cleanup emission data out. This uses either a stack + // array or malloc'd memory, depending on the size, which is + // behavior that SmallVector would provide, if we could use it + // here. Unfortunately, if you ask for a SmallVector, the + // alignment isn't sufficient. + auto *CleanupSource = reinterpret_cast(Scope.getCleanupBuffer()); + alignas(EHScopeStack::ScopeStackAlignment) char + CleanupBufferStack[8 * sizeof(void *)]; + std::unique_ptr CleanupBufferHeap; + size_t CleanupSize = Scope.getCleanupSize(); + EHScopeStack::Cleanup *Fn; + + if (CleanupSize <= sizeof(CleanupBufferStack)) { + memcpy(CleanupBufferStack, CleanupSource, CleanupSize); + Fn = reinterpret_cast(CleanupBufferStack); + } else { + CleanupBufferHeap.reset(new char[CleanupSize]); + memcpy(CleanupBufferHeap.get(), CleanupSource, CleanupSize); + Fn = reinterpret_cast(CleanupBufferHeap.get()); + } + + EHScopeStack::Cleanup::Flags cleanupFlags; + if (Scope.isNormalCleanup()) + cleanupFlags.setIsNormalCleanupKind(); + if (Scope.isEHCleanup()) + cleanupFlags.setIsEHCleanupKind(); + + // Under -EHa, invoke seh.scope.end() to mark scope end before dtor + bool IsEHa = getLangOpts().EHAsynch && !Scope.isLifetimeMarker(); + // const EHPersonality &Personality = EHPersonality::get(*this); + if (!RequiresNormalCleanup) { + llvm_unreachable("NYI"); + } else { + // If we have a fallthrough and no other need for the cleanup, + // emit it directly. + if (HasFallthrough && !HasPrebranchedFallthrough && !HasFixups && + !HasExistingBranches) { + + // mark SEH scope end for fall-through flow + if (IsEHa) { + llvm_unreachable("NYI"); + } + + destroyOptimisticNormalEntry(*this, Scope); + EHStack.popCleanup(); + buildCleanup(*this, Fn, cleanupFlags, NormalActiveFlag); + + // Otherwise, the best approach is to thread everything through + // the cleanup block and then try to clean up after ourselves. + } else { + llvm_unreachable("NYI"); + } + } + + assert(EHStack.hasNormalCleanups() || EHStack.getNumBranchFixups() == 0); + + // Emit the EH cleanup if required. + if (RequiresEHCleanup) { + llvm_unreachable("NYI"); + } +} + +/// Pops cleanup blocks until the given savepoint is reached. +void CIRGenFunction::PopCleanupBlocks( + EHScopeStack::stable_iterator Old, + std::initializer_list ValuesToReload) { + assert(Old.isValid()); + + bool HadBranches = false; + while (EHStack.stable_begin() != Old) { + EHCleanupScope &Scope = cast(*EHStack.begin()); + HadBranches |= Scope.hasBranches(); + + // As long as Old strictly encloses the scope's enclosing normal + // cleanup, we're going to emit another normal cleanup which + // fallthrough can propagate through. + bool FallThroughIsBranchThrough = + Old.strictlyEncloses(Scope.getEnclosingNormalCleanup()); + + PopCleanupBlock(FallThroughIsBranchThrough); + } + + // If we didn't have any branches, the insertion point before cleanups must + // dominate the current insertion point and we don't need to reload any + // values. + if (!HadBranches) + return; + + llvm_unreachable("NYI"); +} + +/// Pops cleanup blocks until the given savepoint is reached, then add the +/// cleanups from the given savepoint in the lifetime-extended cleanups stack. +void CIRGenFunction::PopCleanupBlocks( + EHScopeStack::stable_iterator Old, size_t OldLifetimeExtendedSize, + std::initializer_list ValuesToReload) { + PopCleanupBlocks(Old, ValuesToReload); + + // Move our deferred cleanups onto the EH stack. + for (size_t I = OldLifetimeExtendedSize, + E = LifetimeExtendedCleanupStack.size(); + I != E; + /**/) { + // Alignment should be guaranteed by the vptrs in the individual cleanups. + assert((I % alignof(LifetimeExtendedCleanupHeader) == 0) && + "misaligned cleanup stack entry"); + + LifetimeExtendedCleanupHeader &Header = + reinterpret_cast( + LifetimeExtendedCleanupStack[I]); + I += sizeof(Header); + + EHStack.pushCopyOfCleanup( + Header.getKind(), &LifetimeExtendedCleanupStack[I], Header.getSize()); + I += Header.getSize(); + + if (Header.isConditional()) { + Address ActiveFlag = + reinterpret_cast
(LifetimeExtendedCleanupStack[I]); + initFullExprCleanupWithFlag(ActiveFlag); + I += sizeof(ActiveFlag); + } + } + LifetimeExtendedCleanupStack.resize(OldLifetimeExtendedSize); +} + +//===----------------------------------------------------------------------===// +// EHScopeStack +//===----------------------------------------------------------------------===// + +void EHScopeStack::Cleanup::anchor() {} + +/// Push an entry of the given size onto this protected-scope stack. +char *EHScopeStack::allocate(size_t Size) { + Size = llvm::alignTo(Size, ScopeStackAlignment); + if (!StartOfBuffer) { + unsigned Capacity = 1024; + while (Capacity < Size) + Capacity *= 2; + StartOfBuffer = new char[Capacity]; + StartOfData = EndOfBuffer = StartOfBuffer + Capacity; + } else if (static_cast(StartOfData - StartOfBuffer) < Size) { + unsigned CurrentCapacity = EndOfBuffer - StartOfBuffer; + unsigned UsedCapacity = CurrentCapacity - (StartOfData - StartOfBuffer); + + unsigned NewCapacity = CurrentCapacity; + do { + NewCapacity *= 2; + } while (NewCapacity < UsedCapacity + Size); + + char *NewStartOfBuffer = new char[NewCapacity]; + char *NewEndOfBuffer = NewStartOfBuffer + NewCapacity; + char *NewStartOfData = NewEndOfBuffer - UsedCapacity; + memcpy(NewStartOfData, StartOfData, UsedCapacity); + delete[] StartOfBuffer; + StartOfBuffer = NewStartOfBuffer; + EndOfBuffer = NewEndOfBuffer; + StartOfData = NewStartOfData; + } + + assert(StartOfBuffer + Size <= StartOfData); + StartOfData -= Size; + return StartOfData; +} + +void *EHScopeStack::pushCleanup(CleanupKind Kind, size_t Size) { + char *Buffer = allocate(EHCleanupScope::getSizeForCleanupSize(Size)); + bool IsNormalCleanup = Kind & NormalCleanup; + bool IsEHCleanup = Kind & EHCleanup; + bool IsLifetimeMarker = Kind & LifetimeMarker; + + // Per C++ [except.terminate], it is implementation-defined whether none, + // some, or all cleanups are called before std::terminate. Thus, when + // terminate is the current EH scope, we may skip adding any EH cleanup + // scopes. + if (InnermostEHScope != stable_end() && + find(InnermostEHScope)->getKind() == EHScope::Terminate) + IsEHCleanup = false; + + EHCleanupScope *Scope = new (Buffer) + EHCleanupScope(IsNormalCleanup, IsEHCleanup, Size, BranchFixups.size(), + InnermostNormalCleanup, InnermostEHScope); + if (IsNormalCleanup) + InnermostNormalCleanup = stable_begin(); + if (IsEHCleanup) + InnermostEHScope = stable_begin(); + if (IsLifetimeMarker) + llvm_unreachable("NYI"); + + // With Windows -EHa, Invoke llvm.seh.scope.begin() for EHCleanup + if (CGF->getLangOpts().EHAsynch && IsEHCleanup && !IsLifetimeMarker && + CGF->getTarget().getCXXABI().isMicrosoft()) + llvm_unreachable("NYI"); + + return Scope->getCleanupBuffer(); +} + +void EHScopeStack::popCleanup() { + assert(!empty() && "popping exception stack when not empty"); + + assert(isa(*begin())); + EHCleanupScope &Cleanup = cast(*begin()); + InnermostNormalCleanup = Cleanup.getEnclosingNormalCleanup(); + InnermostEHScope = Cleanup.getEnclosingEHScope(); + deallocate(Cleanup.getAllocatedSize()); + + // Destroy the cleanup. + Cleanup.Destroy(); + + // Check whether we can shrink the branch-fixups stack. + if (!BranchFixups.empty()) { + // If we no longer have any normal cleanups, all the fixups are + // complete. + if (!hasNormalCleanups()) + BranchFixups.clear(); + + // Otherwise we can still trim out unnecessary nulls. + else + popNullFixups(); + } +} + +void EHScopeStack::deallocate(size_t Size) { + StartOfData += llvm::alignTo(Size, ScopeStackAlignment); +} + +/// Remove any 'null' fixups on the stack. However, we can't pop more +/// fixups than the fixup depth on the innermost normal cleanup, or +/// else fixups that we try to add to that cleanup will end up in the +/// wrong place. We *could* try to shrink fixup depths, but that's +/// actually a lot of work for little benefit. +void EHScopeStack::popNullFixups() { + // We expect this to only be called when there's still an innermost + // normal cleanup; otherwise there really shouldn't be any fixups. + llvm_unreachable("NYI"); +} + +bool EHScopeStack::requiresLandingPad() const { + for (stable_iterator si = getInnermostEHScope(); si != stable_end();) { + // Skip lifetime markers. + if (auto *cleanup = dyn_cast(&*find(si))) + if (cleanup->isLifetimeMarker()) { + si = cleanup->getEnclosingEHScope(); + continue; + } + return true; + } + + return false; +} + +EHCatchScope *EHScopeStack::pushCatch(unsigned numHandlers) { + char *buffer = allocate(EHCatchScope::getSizeForNumHandlers(numHandlers)); + EHCatchScope *scope = + new (buffer) EHCatchScope(numHandlers, InnermostEHScope); + InnermostEHScope = stable_begin(); + return scope; +} \ No newline at end of file diff --git a/clang/lib/CIR/CodeGen/CIRGenCleanup.h b/clang/lib/CIR/CodeGen/CIRGenCleanup.h new file mode 100644 index 000000000000..fa3b8cde0d25 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCleanup.h @@ -0,0 +1,617 @@ +//===-- CIRGenCleanup.h - Classes for cleanups CIR generation ---*- C++ -*-===// +// +// 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 support the generation of CIR for cleanups, initially based +// on LLVM IR cleanup handling, but ought to change as CIR evolves. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CODEGEN_CGCLEANUP_H +#define LLVM_CLANG_LIB_CIR_CODEGEN_CGCLEANUP_H + +#include "Address.h" +#include "EHScopeStack.h" +#include "mlir/IR/Value.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" + +namespace clang { +class FunctionDecl; +} + +namespace cir { +class CIRGenModule; +class CIRGenFunction; + +/// The MS C++ ABI needs a pointer to RTTI data plus some flags to describe the +/// type of a catch handler, so we use this wrapper. +struct CatchTypeInfo { + mlir::TypedAttr RTTI; + unsigned Flags; +}; + +/// A protected scope for zero-cost EH handling. +class EHScope { + mlir::Block *CachedLandingPad; + mlir::Block *CachedEHDispatchBlock; + + EHScopeStack::stable_iterator EnclosingEHScope; + + class CommonBitFields { + friend class EHScope; + unsigned Kind : 3; + }; + enum { NumCommonBits = 3 }; + +protected: + class CatchBitFields { + friend class EHCatchScope; + unsigned : NumCommonBits; + + unsigned NumHandlers : 32 - NumCommonBits; + }; + + class CleanupBitFields { + friend class EHCleanupScope; + unsigned : NumCommonBits; + + /// Whether this cleanup needs to be run along normal edges. + unsigned IsNormalCleanup : 1; + + /// Whether this cleanup needs to be run along exception edges. + unsigned IsEHCleanup : 1; + + /// Whether this cleanup is currently active. + unsigned IsActive : 1; + + /// Whether this cleanup is a lifetime marker + unsigned IsLifetimeMarker : 1; + + /// Whether the normal cleanup should test the activation flag. + unsigned TestFlagInNormalCleanup : 1; + + /// Whether the EH cleanup should test the activation flag. + unsigned TestFlagInEHCleanup : 1; + + /// The amount of extra storage needed by the Cleanup. + /// Always a multiple of the scope-stack alignment. + unsigned CleanupSize : 12; + }; + + class FilterBitFields { + friend class EHFilterScope; + unsigned : NumCommonBits; + + unsigned NumFilters : 32 - NumCommonBits; + }; + + union { + CommonBitFields CommonBits; + CatchBitFields CatchBits; + CleanupBitFields CleanupBits; + FilterBitFields FilterBits; + }; + +public: + enum Kind { Cleanup, Catch, Terminate, Filter }; + + EHScope(Kind kind, EHScopeStack::stable_iterator enclosingEHScope) + : CachedLandingPad(nullptr), CachedEHDispatchBlock(nullptr), + EnclosingEHScope(enclosingEHScope) { + CommonBits.Kind = kind; + } + + Kind getKind() const { return static_cast(CommonBits.Kind); } + + mlir::Block *getCachedLandingPad() const { return CachedLandingPad; } + + void setCachedLandingPad(mlir::Block *block) { CachedLandingPad = block; } + + mlir::Block *getCachedEHDispatchBlock() const { + return CachedEHDispatchBlock; + } + + void setCachedEHDispatchBlock(mlir::Block *block) { + CachedEHDispatchBlock = block; + } + + bool hasEHBranches() const { + if (mlir::Block *block = getCachedEHDispatchBlock()) + return !block->use_empty(); + return false; + } + + EHScopeStack::stable_iterator getEnclosingEHScope() const { + return EnclosingEHScope; + } +}; + +/// A scope which attempts to handle some, possibly all, types of +/// exceptions. +/// +/// Objective C \@finally blocks are represented using a cleanup scope +/// after the catch scope. +class EHCatchScope : public EHScope { + // In effect, we have a flexible array member + // Handler Handlers[0]; + // But that's only standard in C99, not C++, so we have to do + // annoying pointer arithmetic instead. + +public: + struct Handler { + /// A type info value, or null (C++ null, not an LLVM null pointer) + /// for a catch-all. + CatchTypeInfo Type; + + /// The catch handler for this type. + mlir::Block *Block; + + bool isCatchAll() const { return Type.RTTI == nullptr; } + }; + +private: + friend class EHScopeStack; + + Handler *getHandlers() { return reinterpret_cast(this + 1); } + + const Handler *getHandlers() const { + return reinterpret_cast(this + 1); + } + +public: + static size_t getSizeForNumHandlers(unsigned N) { + return sizeof(EHCatchScope) + N * sizeof(Handler); + } + + EHCatchScope(unsigned numHandlers, + EHScopeStack::stable_iterator enclosingEHScope) + : EHScope(Catch, enclosingEHScope) { + CatchBits.NumHandlers = numHandlers; + assert(CatchBits.NumHandlers == numHandlers && "NumHandlers overflow?"); + } + + unsigned getNumHandlers() const { return CatchBits.NumHandlers; } + + void setCatchAllHandler(unsigned I, mlir::Block *Block) { + setHandler(I, CatchTypeInfo{nullptr, 0}, Block); + } + + void setHandler(unsigned I, mlir::TypedAttr Type, mlir::Block *Block) { + assert(I < getNumHandlers()); + getHandlers()[I].Type = CatchTypeInfo{Type, 0}; + getHandlers()[I].Block = Block; + } + + void setHandler(unsigned I, CatchTypeInfo Type, mlir::Block *Block) { + assert(I < getNumHandlers()); + getHandlers()[I].Type = Type; + getHandlers()[I].Block = Block; + } + + const Handler &getHandler(unsigned I) const { + assert(I < getNumHandlers()); + return getHandlers()[I]; + } + + // Clear all handler blocks. + // FIXME: it's better to always call clearHandlerBlocks in DTOR and have a + // 'takeHandler' or some such function which removes ownership from the + // EHCatchScope object if the handlers should live longer than EHCatchScope. + void clearHandlerBlocks() { + for (unsigned I = 0, N = getNumHandlers(); I != N; ++I) + delete getHandler(I).Block; + } + + typedef const Handler *iterator; + iterator begin() const { return getHandlers(); } + iterator end() const { return getHandlers() + getNumHandlers(); } + + static bool classof(const EHScope *Scope) { + return Scope->getKind() == Catch; + } +}; + +/// A cleanup scope which generates the cleanup blocks lazily. +class alignas(8) EHCleanupScope : public EHScope { + /// The nearest normal cleanup scope enclosing this one. + EHScopeStack::stable_iterator EnclosingNormal; + + /// The nearest EH scope enclosing this one. + EHScopeStack::stable_iterator EnclosingEH; + + /// The dual entry/exit block along the normal edge. This is lazily + /// created if needed before the cleanup is popped. + mlir::Block *NormalBlock; + + /// An optional i1 variable indicating whether this cleanup has been + /// activated yet. + Address ActiveFlag; + + /// Extra information required for cleanups that have resolved + /// branches through them. This has to be allocated on the side + /// because everything on the cleanup stack has be trivially + /// movable. + struct ExtInfo { + /// The destinations of normal branch-afters and branch-throughs. + llvm::SmallPtrSet Branches; + + /// Normal branch-afters. + llvm::SmallVector, 4> BranchAfters; + }; + mutable struct ExtInfo *ExtInfo; + + /// The number of fixups required by enclosing scopes (not including + /// this one). If this is the top cleanup scope, all the fixups + /// from this index onwards belong to this scope. + unsigned FixupDepth; + + struct ExtInfo &getExtInfo() { + if (!ExtInfo) + ExtInfo = new struct ExtInfo(); + return *ExtInfo; + } + + const struct ExtInfo &getExtInfo() const { + if (!ExtInfo) + ExtInfo = new struct ExtInfo(); + return *ExtInfo; + } + +public: + /// Gets the size required for a lazy cleanup scope with the given + /// cleanup-data requirements. + static size_t getSizeForCleanupSize(size_t Size) { + return sizeof(EHCleanupScope) + Size; + } + + size_t getAllocatedSize() const { + return sizeof(EHCleanupScope) + CleanupBits.CleanupSize; + } + + EHCleanupScope(bool isNormal, bool isEH, unsigned cleanupSize, + unsigned fixupDepth, + EHScopeStack::stable_iterator enclosingNormal, + EHScopeStack::stable_iterator enclosingEH) + : EHScope(EHScope::Cleanup, enclosingEH), + EnclosingNormal(enclosingNormal), NormalBlock(nullptr), + ActiveFlag(Address::invalid()), ExtInfo(nullptr), + FixupDepth(fixupDepth) { + CleanupBits.IsNormalCleanup = isNormal; + CleanupBits.IsEHCleanup = isEH; + CleanupBits.IsActive = true; + CleanupBits.IsLifetimeMarker = false; + CleanupBits.TestFlagInNormalCleanup = false; + CleanupBits.TestFlagInEHCleanup = false; + CleanupBits.CleanupSize = cleanupSize; + + assert(CleanupBits.CleanupSize == cleanupSize && "cleanup size overflow"); + } + + void Destroy() { delete ExtInfo; } + // Objects of EHCleanupScope are not destructed. Use Destroy(). + ~EHCleanupScope() = delete; + + bool isNormalCleanup() const { return CleanupBits.IsNormalCleanup; } + mlir::Block *getNormalBlock() const { return NormalBlock; } + void setNormalBlock(mlir::Block *BB) { NormalBlock = BB; } + + bool isEHCleanup() const { return CleanupBits.IsEHCleanup; } + + bool isActive() const { return CleanupBits.IsActive; } + void setActive(bool A) { CleanupBits.IsActive = A; } + + bool isLifetimeMarker() const { return CleanupBits.IsLifetimeMarker; } + void setLifetimeMarker() { CleanupBits.IsLifetimeMarker = true; } + + bool hasActiveFlag() const { return ActiveFlag.isValid(); } + Address getActiveFlag() const { return ActiveFlag; } + void setActiveFlag(Address Var) { + assert(Var.getAlignment().isOne()); + ActiveFlag = Var; + } + + void setTestFlagInNormalCleanup() { + CleanupBits.TestFlagInNormalCleanup = true; + } + bool shouldTestFlagInNormalCleanup() const { + return CleanupBits.TestFlagInNormalCleanup; + } + + void setTestFlagInEHCleanup() { CleanupBits.TestFlagInEHCleanup = true; } + bool shouldTestFlagInEHCleanup() const { + return CleanupBits.TestFlagInEHCleanup; + } + + unsigned getFixupDepth() const { return FixupDepth; } + EHScopeStack::stable_iterator getEnclosingNormalCleanup() const { + return EnclosingNormal; + } + + size_t getCleanupSize() const { return CleanupBits.CleanupSize; } + void *getCleanupBuffer() { return this + 1; } + + EHScopeStack::Cleanup *getCleanup() { + return reinterpret_cast(getCleanupBuffer()); + } + + /// True if this cleanup scope has any branch-afters or branch-throughs. + bool hasBranches() const { return ExtInfo && !ExtInfo->Branches.empty(); } + + /// Add a branch-after to this cleanup scope. A branch-after is a + /// branch from a point protected by this (normal) cleanup to a + /// point in the normal cleanup scope immediately containing it. + /// For example, + /// for (;;) { A a; break; } + /// contains a branch-after. + /// + /// Branch-afters each have their own destination out of the + /// cleanup, guaranteed distinct from anything else threaded through + /// it. Therefore branch-afters usually force a switch after the + /// cleanup. + void addBranchAfter(mlir::Value Index, mlir::Block *Block) { + struct ExtInfo &ExtInfo = getExtInfo(); + if (ExtInfo.Branches.insert(Block).second) + ExtInfo.BranchAfters.push_back(std::make_pair(Block, Index)); + } + + /// Return the number of unique branch-afters on this scope. + unsigned getNumBranchAfters() const { + return ExtInfo ? ExtInfo->BranchAfters.size() : 0; + } + + mlir::Block *getBranchAfterBlock(unsigned I) const { + assert(I < getNumBranchAfters()); + return ExtInfo->BranchAfters[I].first; + } + + mlir::Value getBranchAfterIndex(unsigned I) const { + assert(I < getNumBranchAfters()); + return ExtInfo->BranchAfters[I].second; + } + + /// Add a branch-through to this cleanup scope. A branch-through is + /// a branch from a scope protected by this (normal) cleanup to an + /// enclosing scope other than the immediately-enclosing normal + /// cleanup scope. + /// + /// In the following example, the branch through B's scope is a + /// branch-through, while the branch through A's scope is a + /// branch-after: + /// for (;;) { A a; B b; break; } + /// + /// All branch-throughs have a common destination out of the + /// cleanup, one possibly shared with the fall-through. Therefore + /// branch-throughs usually don't force a switch after the cleanup. + /// + /// \return true if the branch-through was new to this scope + bool addBranchThrough(mlir::Block *Block) { + return getExtInfo().Branches.insert(Block).second; + } + + /// Determines if this cleanup scope has any branch throughs. + bool hasBranchThroughs() const { + if (!ExtInfo) + return false; + return (ExtInfo->BranchAfters.size() != ExtInfo->Branches.size()); + } + + static bool classof(const EHScope *Scope) { + return (Scope->getKind() == Cleanup); + } +}; +// NOTE: there's a bunch of different data classes tacked on after an +// EHCleanupScope. It is asserted (in EHScopeStack::pushCleanup*) that +// they don't require greater alignment than ScopeStackAlignment. So, +// EHCleanupScope ought to have alignment equal to that -- not more +// (would be misaligned by the stack allocator), and not less (would +// break the appended classes). +static_assert(alignof(EHCleanupScope) == EHScopeStack::ScopeStackAlignment, + "EHCleanupScope expected alignment"); + +/// An exceptions scope which filters exceptions thrown through it. +/// Only exceptions matching the filter types will be permitted to be +/// thrown. +/// +/// This is used to implement C++ exception specifications. +class EHFilterScope : public EHScope { + // Essentially ends in a flexible array member: + // mlir::Value FilterTypes[0]; + + mlir::Value *getFilters() { + return reinterpret_cast(this + 1); + } + + mlir::Value const *getFilters() const { + return reinterpret_cast(this + 1); + } + +public: + EHFilterScope(unsigned numFilters) + : EHScope(Filter, EHScopeStack::stable_end()) { + FilterBits.NumFilters = numFilters; + assert(FilterBits.NumFilters == numFilters && "NumFilters overflow"); + } + + static size_t getSizeForNumFilters(unsigned numFilters) { + return sizeof(EHFilterScope) + numFilters * sizeof(mlir::Value); + } + + unsigned getNumFilters() const { return FilterBits.NumFilters; } + + void setFilter(unsigned i, mlir::Value filterValue) { + assert(i < getNumFilters()); + getFilters()[i] = filterValue; + } + + mlir::Value getFilter(unsigned i) const { + assert(i < getNumFilters()); + return getFilters()[i]; + } + + static bool classof(const EHScope *scope) { + return scope->getKind() == Filter; + } +}; + +/// An exceptions scope which calls std::terminate if any exception +/// reaches it. +class EHTerminateScope : public EHScope { +public: + EHTerminateScope(EHScopeStack::stable_iterator enclosingEHScope) + : EHScope(Terminate, enclosingEHScope) {} + static size_t getSize() { return sizeof(EHTerminateScope); } + + static bool classof(const EHScope *scope) { + return scope->getKind() == Terminate; + } +}; + +/// A non-stable pointer into the scope stack. +class EHScopeStack::iterator { + char *Ptr; + + friend class EHScopeStack; + explicit iterator(char *Ptr) : Ptr(Ptr) {} + +public: + iterator() : Ptr(nullptr) {} + + EHScope *get() const { return reinterpret_cast(Ptr); } + + EHScope *operator->() const { return get(); } + EHScope &operator*() const { return *get(); } + + iterator &operator++() { + size_t Size; + switch (get()->getKind()) { + case EHScope::Catch: + Size = EHCatchScope::getSizeForNumHandlers( + static_cast(get())->getNumHandlers()); + break; + + case EHScope::Filter: + Size = EHFilterScope::getSizeForNumFilters( + static_cast(get())->getNumFilters()); + break; + + case EHScope::Cleanup: + Size = static_cast(get())->getAllocatedSize(); + break; + + case EHScope::Terminate: + Size = EHTerminateScope::getSize(); + break; + } + Ptr += llvm::alignTo(Size, ScopeStackAlignment); + return *this; + } + + iterator next() { + iterator copy = *this; + ++copy; + return copy; + } + + iterator operator++(int) { + iterator copy = *this; + operator++(); + return copy; + } + + bool encloses(iterator other) const { return Ptr >= other.Ptr; } + bool strictlyEncloses(iterator other) const { return Ptr > other.Ptr; } + + bool operator==(iterator other) const { return Ptr == other.Ptr; } + bool operator!=(iterator other) const { return Ptr != other.Ptr; } +}; + +inline EHScopeStack::iterator EHScopeStack::begin() const { + return iterator(StartOfData); +} + +inline EHScopeStack::iterator EHScopeStack::end() const { + return iterator(EndOfBuffer); +} + +inline void EHScopeStack::popCatch() { + assert(!empty() && "popping exception stack when not empty"); + + EHCatchScope &scope = llvm::cast(*begin()); + InnermostEHScope = scope.getEnclosingEHScope(); + deallocate(EHCatchScope::getSizeForNumHandlers(scope.getNumHandlers())); +} + +inline void EHScopeStack::popTerminate() { + assert(!empty() && "popping exception stack when not empty"); + + EHTerminateScope &scope = llvm::cast(*begin()); + InnermostEHScope = scope.getEnclosingEHScope(); + deallocate(EHTerminateScope::getSize()); +} + +inline EHScopeStack::iterator EHScopeStack::find(stable_iterator sp) const { + assert(sp.isValid() && "finding invalid savepoint"); + assert(sp.Size <= stable_begin().Size && "finding savepoint after pop"); + return iterator(EndOfBuffer - sp.Size); +} + +inline EHScopeStack::stable_iterator +EHScopeStack::stabilize(iterator ir) const { + assert(StartOfData <= ir.Ptr && ir.Ptr <= EndOfBuffer); + return stable_iterator(EndOfBuffer - ir.Ptr); +} + +/// The exceptions personality for a function. +struct EHPersonality { + const char *PersonalityFn; + + // If this is non-null, this personality requires a non-standard + // function for rethrowing an exception after a catchall cleanup. + // This function must have prototype void(void*). + const char *CatchallRethrowFn; + + static const EHPersonality &get(CIRGenModule &CGM, + const clang::FunctionDecl *FD); + static const EHPersonality &get(CIRGenFunction &CGF); + + static const EHPersonality GNU_C; + static const EHPersonality GNU_C_SJLJ; + static const EHPersonality GNU_C_SEH; + static const EHPersonality GNU_ObjC; + static const EHPersonality GNU_ObjC_SJLJ; + static const EHPersonality GNU_ObjC_SEH; + static const EHPersonality GNUstep_ObjC; + static const EHPersonality GNU_ObjCXX; + static const EHPersonality NeXT_ObjC; + static const EHPersonality GNU_CPlusPlus; + static const EHPersonality GNU_CPlusPlus_SJLJ; + static const EHPersonality GNU_CPlusPlus_SEH; + static const EHPersonality MSVC_except_handler; + static const EHPersonality MSVC_C_specific_handler; + static const EHPersonality MSVC_CxxFrameHandler3; + static const EHPersonality GNU_Wasm_CPlusPlus; + static const EHPersonality XL_CPlusPlus; + + /// Does this personality use landingpads or the family of pad instructions + /// designed to form funclets? + bool usesFuncletPads() const { + return isMSVCPersonality() || isWasmPersonality(); + } + + bool isMSVCPersonality() const { + return this == &MSVC_except_handler || this == &MSVC_C_specific_handler || + this == &MSVC_CxxFrameHandler3; + } + + bool isWasmPersonality() const { return this == &GNU_Wasm_CPlusPlus; } + + bool isMSVCXXPersonality() const { return this == &MSVC_CxxFrameHandler3; } +}; +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp new file mode 100644 index 000000000000..629e186a5f2b --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp @@ -0,0 +1,584 @@ +//===----- CGCoroutine.cpp - Emit CIR Code for C++ coroutines -------------===// +// +// 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 dealing with C++ code generation of coroutines. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenFunction.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/ADT/ScopeExit.h" + +using namespace clang; +using namespace cir; + +struct cir::CGCoroData { + // What is the current await expression kind and how many + // await/yield expressions were encountered so far. + // These are used to generate pretty labels for await expressions in LLVM IR. + mlir::cir::AwaitKind CurrentAwaitKind = mlir::cir::AwaitKind::init; + + // Stores the __builtin_coro_id emitted in the function so that we can supply + // it as the first argument to other builtins. + mlir::cir::CallOp CoroId = nullptr; + + // Stores the result of __builtin_coro_begin call. + mlir::Value CoroBegin = nullptr; + + // Stores the insertion point for final suspend, this happens after the + // promise call (return_xxx promise member) but before a cir.br to the return + // block. + mlir::Operation *FinalSuspendInsPoint; + + // How many co_return statements are in the coroutine. Used to decide whether + // we need to add co_return; equivalent at the end of the user authored body. + unsigned CoreturnCount = 0; + + // The promise type's 'unhandled_exception' handler, if it defines one. + Stmt *ExceptionHandler = nullptr; +}; + +// Defining these here allows to keep CGCoroData private to this file. +CIRGenFunction::CGCoroInfo::CGCoroInfo() {} +CIRGenFunction::CGCoroInfo::~CGCoroInfo() {} + +static void createCoroData(CIRGenFunction &CGF, + CIRGenFunction::CGCoroInfo &CurCoro, + mlir::cir::CallOp CoroId) { + if (CurCoro.Data) { + llvm_unreachable("EmitCoroutineBodyStatement called twice?"); + + return; + } + + CurCoro.Data = std::unique_ptr(new CGCoroData); + CurCoro.Data->CoroId = CoroId; +} + +namespace { +// FIXME: both GetParamRef and ParamReferenceReplacerRAII are good template +// candidates to be shared among LLVM / CIR codegen. + +// Hunts for the parameter reference in the parameter copy/move declaration. +struct GetParamRef : public StmtVisitor { +public: + DeclRefExpr *Expr = nullptr; + GetParamRef() {} + void VisitDeclRefExpr(DeclRefExpr *E) { + assert(Expr == nullptr && "multilple declref in param move"); + Expr = E; + } + void VisitStmt(Stmt *S) { + for (auto *C : S->children()) { + if (C) + Visit(C); + } + } +}; + +// This class replaces references to parameters to their copies by changing +// the addresses in CGF.LocalDeclMap and restoring back the original values in +// its destructor. +struct ParamReferenceReplacerRAII { + CIRGenFunction::DeclMapTy SavedLocals; + CIRGenFunction::DeclMapTy &LocalDeclMap; + + ParamReferenceReplacerRAII(CIRGenFunction::DeclMapTy &LocalDeclMap) + : LocalDeclMap(LocalDeclMap) {} + + void addCopy(DeclStmt const *PM) { + // Figure out what param it refers to. + + assert(PM->isSingleDecl()); + VarDecl const *VD = static_cast(PM->getSingleDecl()); + Expr const *InitExpr = VD->getInit(); + GetParamRef Visitor; + Visitor.Visit(const_cast(InitExpr)); + assert(Visitor.Expr); + DeclRefExpr *DREOrig = Visitor.Expr; + auto *PD = DREOrig->getDecl(); + + auto it = LocalDeclMap.find(PD); + assert(it != LocalDeclMap.end() && "parameter is not found"); + SavedLocals.insert({PD, it->second}); + + auto copyIt = LocalDeclMap.find(VD); + assert(copyIt != LocalDeclMap.end() && "parameter copy is not found"); + it->second = copyIt->getSecond(); + } + + ~ParamReferenceReplacerRAII() { + for (auto &&SavedLocal : SavedLocals) { + LocalDeclMap.insert({SavedLocal.first, SavedLocal.second}); + } + } +}; +} // namespace + +// Emit coroutine intrinsic and patch up arguments of the token type. +RValue CIRGenFunction::buildCoroutineIntrinsic(const CallExpr *E, + unsigned int IID) { + llvm_unreachable("NYI"); +} + +RValue CIRGenFunction::buildCoroutineFrame() { + if (CurCoro.Data && CurCoro.Data->CoroBegin) { + return RValue::get(CurCoro.Data->CoroBegin); + } + llvm_unreachable("NYI"); +} + +static mlir::LogicalResult buildBodyAndFallthrough( + CIRGenFunction &CGF, const CoroutineBodyStmt &S, Stmt *Body, + const CIRGenFunction::LexicalScopeContext *currLexScope) { + if (CGF.buildStmt(Body, /*useCurrentScope=*/true).failed()) + return mlir::failure(); + // Note that LLVM checks CanFallthrough by looking into the availability + // of the insert block which is kinda brittle and unintuitive, seems to be + // related with how landing pads are handled. + // + // CIRGen handles this by checking pre-existing co_returns in the current + // scope instead. Are we missing anything? + // + // From LLVM IR Gen: const bool CanFallthrough = Builder.GetInsertBlock(); + const bool CanFallthrough = !currLexScope->hasCoreturn(); + if (CanFallthrough) + if (Stmt *OnFallthrough = S.getFallthroughHandler()) + if (CGF.buildStmt(OnFallthrough, /*useCurrentScope=*/true).failed()) + return mlir::failure(); + + return mlir::success(); +} + +mlir::cir::CallOp CIRGenFunction::buildCoroIDBuiltinCall(mlir::Location loc, + mlir::Value nullPtr) { + auto int32Ty = builder.getUInt32Ty(); + + auto &TI = CGM.getASTContext().getTargetInfo(); + unsigned NewAlign = TI.getNewAlign() / TI.getCharWidth(); + + mlir::Operation *builtin = CGM.getGlobalValue(CGM.builtinCoroId); + + mlir::cir::FuncOp fnOp; + if (!builtin) { + fnOp = CGM.createCIRFunction( + loc, CGM.builtinCoroId, + mlir::cir::FuncType::get({int32Ty, VoidPtrTy, VoidPtrTy, VoidPtrTy}, + int32Ty), + /*FD=*/nullptr); + assert(fnOp && "should always succeed"); + fnOp.setBuiltinAttr(mlir::UnitAttr::get(builder.getContext())); + } else + fnOp = cast(builtin); + + return builder.create( + loc, fnOp, + mlir::ValueRange{builder.getUInt32(NewAlign, loc), nullPtr, nullPtr, + nullPtr}); +} + +mlir::cir::CallOp +CIRGenFunction::buildCoroAllocBuiltinCall(mlir::Location loc) { + auto boolTy = builder.getBoolTy(); + auto int32Ty = builder.getUInt32Ty(); + + mlir::Operation *builtin = CGM.getGlobalValue(CGM.builtinCoroAlloc); + + mlir::cir::FuncOp fnOp; + if (!builtin) { + fnOp = CGM.createCIRFunction( + loc, CGM.builtinCoroAlloc, + mlir::cir::FuncType::get({int32Ty}, boolTy), + /*FD=*/nullptr); + assert(fnOp && "should always succeed"); + fnOp.setBuiltinAttr(mlir::UnitAttr::get(builder.getContext())); + } else + fnOp = cast(builtin); + + return builder.create( + loc, fnOp, mlir::ValueRange{CurCoro.Data->CoroId.getResult(0)}); +} + +mlir::cir::CallOp +CIRGenFunction::buildCoroBeginBuiltinCall(mlir::Location loc, + mlir::Value coroframeAddr) { + auto int32Ty = builder.getUInt32Ty(); + mlir::Operation *builtin = CGM.getGlobalValue(CGM.builtinCoroBegin); + + mlir::cir::FuncOp fnOp; + if (!builtin) { + fnOp = CGM.createCIRFunction( + loc, CGM.builtinCoroBegin, + mlir::cir::FuncType::get({int32Ty, VoidPtrTy}, + VoidPtrTy), + /*FD=*/nullptr); + assert(fnOp && "should always succeed"); + fnOp.setBuiltinAttr(mlir::UnitAttr::get(builder.getContext())); + } else + fnOp = cast(builtin); + + return builder.create( + loc, fnOp, + mlir::ValueRange{CurCoro.Data->CoroId.getResult(0), coroframeAddr}); +} + +mlir::cir::CallOp CIRGenFunction::buildCoroEndBuiltinCall(mlir::Location loc, + mlir::Value nullPtr) { + auto boolTy = builder.getBoolTy(); + mlir::Operation *builtin = CGM.getGlobalValue(CGM.builtinCoroEnd); + + mlir::cir::FuncOp fnOp; + if (!builtin) { + fnOp = CGM.createCIRFunction( + loc, CGM.builtinCoroEnd, + mlir::cir::FuncType::get({VoidPtrTy, boolTy}, boolTy), + /*FD=*/nullptr); + assert(fnOp && "should always succeed"); + fnOp.setBuiltinAttr(mlir::UnitAttr::get(builder.getContext())); + } else + fnOp = cast(builtin); + + return builder.create( + loc, fnOp, mlir::ValueRange{nullPtr, builder.getBool(false, loc)}); +} + +mlir::LogicalResult +CIRGenFunction::buildCoroutineBody(const CoroutineBodyStmt &S) { + auto openCurlyLoc = getLoc(S.getBeginLoc()); + auto nullPtrCst = builder.getNullPtr(VoidPtrTy, openCurlyLoc); + + auto Fn = dyn_cast(CurFn); + assert(Fn && "other callables NYI"); + Fn.setCoroutineAttr(mlir::UnitAttr::get(builder.getContext())); + auto coroId = buildCoroIDBuiltinCall(openCurlyLoc, nullPtrCst); + createCoroData(*this, CurCoro, coroId); + + // Backend is allowed to elide memory allocations, to help it, emit + // auto mem = coro.alloc() ? 0 : ... allocation code ...; + auto coroAlloc = buildCoroAllocBuiltinCall(openCurlyLoc); + + // Initialize address of coroutine frame to null + auto astVoidPtrTy = CGM.getASTContext().VoidPtrTy; + auto allocaTy = getTypes().convertTypeForMem(astVoidPtrTy); + Address coroFrame = + CreateTempAlloca(allocaTy, getContext().getTypeAlignInChars(astVoidPtrTy), + openCurlyLoc, "__coro_frame_addr", + /*ArraySize=*/nullptr); + + auto storeAddr = coroFrame.getPointer(); + builder.create(openCurlyLoc, nullPtrCst, storeAddr); + builder.create(openCurlyLoc, coroAlloc.getResult(0), + /*withElseRegion=*/false, + /*thenBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + builder.create( + loc, buildScalarExpr(S.getAllocate()), + storeAddr); + builder.create(loc); + }); + + CurCoro.Data->CoroBegin = + buildCoroBeginBuiltinCall( + openCurlyLoc, + builder.create(openCurlyLoc, allocaTy, storeAddr)) + .getResult(0); + + // Handle allocation failure if 'ReturnStmtOnAllocFailure' was provided. + if (auto *RetOnAllocFailure = S.getReturnStmtOnAllocFailure()) + llvm_unreachable("NYI"); + + { + // FIXME(cir): create a new scope to copy out the params? + // LLVM create scope cleanups here, but might be due to the use + // of many basic blocks? + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + ParamReferenceReplacerRAII ParamReplacer(LocalDeclMap); + + // Create mapping between parameters and copy-params for coroutine + // function. + llvm::ArrayRef ParamMoves = S.getParamMoves(); + assert((ParamMoves.size() == 0 || (ParamMoves.size() == FnArgs.size())) && + "ParamMoves and FnArgs should be the same size for coroutine " + "function"); + // For zipping the arg map into debug info. + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + + // Create parameter copies. We do it before creating a promise, since an + // evolution of coroutine TS may allow promise constructor to observe + // parameter copies. + for (auto *PM : S.getParamMoves()) { + if (buildStmt(PM, /*useCurrentScope=*/true).failed()) + return mlir::failure(); + ParamReplacer.addCopy(cast(PM)); + } + + if (buildStmt(S.getPromiseDeclStmt(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + + // ReturnValue should be valid as long as the coroutine's return type + // is not void. The assertion could help us to reduce the check later. + assert(ReturnValue.isValid() == (bool)S.getReturnStmt()); + // Now we have the promise, initialize the GRO. + // We need to emit `get_return_object` first. According to: + // [dcl.fct.def.coroutine]p7 + // The call to get_return_­object is sequenced before the call to + // initial_suspend and is invoked at most once. + // + // So we couldn't emit return value when we emit return statment, + // otherwise the call to get_return_object wouldn't be in front + // of initial_suspend. + if (ReturnValue.isValid()) { + buildAnyExprToMem(S.getReturnValue(), ReturnValue, + S.getReturnValue()->getType().getQualifiers(), + /*IsInit*/ true); + } + + // FIXME(cir): EHStack.pushCleanup(EHCleanup); + CurCoro.Data->CurrentAwaitKind = mlir::cir::AwaitKind::init; + if (buildStmt(S.getInitSuspendStmt(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + + CurCoro.Data->CurrentAwaitKind = mlir::cir::AwaitKind::user; + + // FIXME(cir): wrap buildBodyAndFallthrough with try/catch bits. + if (S.getExceptionHandler()) + assert(!UnimplementedFeature::unhandledException() && "NYI"); + if (buildBodyAndFallthrough(*this, S, S.getBody(), currLexScope).failed()) + return mlir::failure(); + + // Note that LLVM checks CanFallthrough by looking into the availability + // of the insert block which is kinda brittle and unintuitive, seems to be + // related with how landing pads are handled. + // + // CIRGen handles this by checking pre-existing co_returns in the current + // scope instead. Are we missing anything? + // + // From LLVM IR Gen: const bool CanFallthrough = Builder.GetInsertBlock(); + const bool CanFallthrough = currLexScope->hasCoreturn(); + const bool HasCoreturns = CurCoro.Data->CoreturnCount > 0; + if (CanFallthrough || HasCoreturns) { + CurCoro.Data->CurrentAwaitKind = mlir::cir::AwaitKind::final; + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPoint(CurCoro.Data->FinalSuspendInsPoint); + if (buildStmt(S.getFinalSuspendStmt(), /*useCurrentScope=*/true) + .failed()) + return mlir::failure(); + } + } + } + return mlir::success(); +} + +static bool memberCallExpressionCanThrow(const Expr *E) { + if (const auto *CE = dyn_cast(E)) + if (const auto *Proto = + CE->getMethodDecl()->getType()->getAs()) + if (isNoexceptExceptionSpec(Proto->getExceptionSpecType()) && + Proto->canThrow() == CT_Cannot) + return false; + return true; +} + +// Given a suspend expression which roughly looks like: +// +// auto && x = CommonExpr(); +// if (!x.await_ready()) { +// x.await_suspend(...); (*) +// } +// x.await_resume(); +// +// where the result of the entire expression is the result of x.await_resume() +// +// (*) If x.await_suspend return type is bool, it allows to veto a suspend: +// if (x.await_suspend(...)) +// llvm_coro_suspend(); +// +// This is more higher level than LLVM codegen, for that one see llvm's +// docs/Coroutines.rst for more details. +namespace { +struct LValueOrRValue { + LValue LV; + RValue RV; +}; +} // namespace +static LValueOrRValue +buildSuspendExpression(CIRGenFunction &CGF, CGCoroData &Coro, + CoroutineSuspendExpr const &S, mlir::cir::AwaitKind Kind, + AggValueSlot aggSlot, bool ignoreResult, + mlir::Block *scopeParentBlock, + mlir::Value &tmpResumeRValAddr, bool forLValue) { + auto *E = S.getCommonExpr(); + + auto awaitBuild = mlir::success(); + LValueOrRValue awaitRes; + + auto Binder = + CIRGenFunction::OpaqueValueMappingData::bind(CGF, S.getOpaqueValue(), E); + auto UnbindOnExit = llvm::make_scope_exit([&] { Binder.unbind(CGF); }); + auto &builder = CGF.getBuilder(); + + [[maybe_unused]] auto awaitOp = builder.create( + CGF.getLoc(S.getSourceRange()), Kind, + /*readyBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + auto *cond = S.getReadyExpr(); + cond = cond->IgnoreParens(); + mlir::Value condV = CGF.evaluateExprAsBool(cond); + + builder.create( + loc, condV, /*withElseRegion=*/false, + /*thenBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + // If expression is ready, no need to suspend, + // `YieldOpKind::NoSuspend` tells control flow to return to + // parent, no more regions to be executed. + builder.create( + loc, mlir::cir::YieldOpKind::NoSuspend); + }); + + if (!condV) { + awaitBuild = mlir::failure(); + return; + } + + // Signals the parent that execution flows to next region. + builder.create(loc); + }, + /*suspendBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + // Note that differently from LLVM codegen we do not emit coro.save + // and coro.suspend here, that should be done as part of lowering this + // to LLVM dialect (or some other MLIR dialect) + + // A invalid suspendRet indicates "void returning await_suspend" + auto suspendRet = CGF.buildScalarExpr(S.getSuspendExpr()); + + // Veto suspension if requested by bool returning await_suspend. + if (suspendRet) { + // From LLVM codegen: + // if (SuspendRet != nullptr && SuspendRet->getType()->isIntegerTy(1)) + llvm_unreachable("NYI"); + } + + // Signals the parent that execution flows to next region. + builder.create(loc); + }, + /*resumeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + // Exception handling requires additional IR. If the 'await_resume' + // function is marked as 'noexcept', we avoid generating this additional + // IR. + CXXTryStmt *TryStmt = nullptr; + if (Coro.ExceptionHandler && Kind == mlir::cir::AwaitKind::init && + memberCallExpressionCanThrow(S.getResumeExpr())) { + llvm_unreachable("NYI"); + } + + // FIXME(cir): the alloca for the resume expr should be placed in the + // enclosing cir.scope instead. + if (forLValue) + awaitRes.LV = CGF.buildLValue(S.getResumeExpr()); + else { + awaitRes.RV = + CGF.buildAnyExpr(S.getResumeExpr(), aggSlot, ignoreResult); + if (!awaitRes.RV.isIgnored()) { + // Create the alloca in the block before the scope wrapping + // cir.await. + tmpResumeRValAddr = CGF.buildAlloca( + "__coawait_resume_rval", awaitRes.RV.getScalarVal().getType(), + loc, CharUnits::One(), + builder.getBestAllocaInsertPoint(scopeParentBlock)); + // Store the rvalue so we can reload it before the promise call. + builder.create(loc, awaitRes.RV.getScalarVal(), + tmpResumeRValAddr); + } + } + + if (TryStmt) { + llvm_unreachable("NYI"); + } + + // Returns control back to parent. + builder.create(loc); + }); + + assert(awaitBuild.succeeded() && "Should know how to codegen"); + return awaitRes; +} + +RValue CIRGenFunction::buildCoawaitExpr(const CoawaitExpr &E, + AggValueSlot aggSlot, + bool ignoreResult) { + RValue rval; + auto scopeLoc = getLoc(E.getSourceRange()); + + // Since we model suspend / resume as an inner region, we must store + // resume scalar results in a tmp alloca, and load it after we build the + // suspend expression. An alternative way to do this would be to make + // every region return a value when promise.return_value() is used, but + // it's a bit awkward given that resume is the only region that actually + // returns a value. + mlir::Block *currEntryBlock = currLexScope->getEntryBlock(); + [[maybe_unused]] mlir::Value tmpResumeRValAddr; + + // No need to explicitly wrap this into a scope since the AST already uses a + // ExprWithCleanups, which will wrap this into a cir.scope anyways. + rval = buildSuspendExpression(*this, *CurCoro.Data, E, + CurCoro.Data->CurrentAwaitKind, aggSlot, + ignoreResult, currEntryBlock, tmpResumeRValAddr, + /*forLValue*/ false) + .RV; + + if (ignoreResult || rval.isIgnored()) + return rval; + + if (rval.isScalar()) { + rval = RValue::get(builder.create( + scopeLoc, rval.getScalarVal().getType(), tmpResumeRValAddr)); + } else if (rval.isAggregate()) { + // This is probably already handled via AggSlot, remove this assertion + // once we have a testcase and prove all pieces work. + llvm_unreachable("NYI"); + } else { // complex + llvm_unreachable("NYI"); + } + return rval; +} + +mlir::LogicalResult CIRGenFunction::buildCoreturnStmt(CoreturnStmt const &S) { + ++CurCoro.Data->CoreturnCount; + currLexScope->setCoreturn(); + + const Expr *RV = S.getOperand(); + if (RV && RV->getType()->isVoidType() && !isa(RV)) { + // Make sure to evaluate the non initlist expression of a co_return + // with a void expression for side effects. + // FIXME(cir): add scope + // RunCleanupsScope cleanupScope(*this); + buildIgnoredExpr(RV); + } + if (buildStmt(S.getPromiseCall(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + // Create a new return block (if not existent) and add a branch to + // it. The actual return instruction is only inserted during current + // scope cleanup handling. + auto loc = getLoc(S.getSourceRange()); + auto *retBlock = currLexScope->getOrCreateRetBlock(*this, loc); + CurCoro.Data->FinalSuspendInsPoint = + builder.create(loc, retBlock); + + // Insert the new block to continue codegen after branch to ret block, + // this will likely be an empty block. + builder.createBlock(builder.getBlock()->getParent()); + + // TODO(cir): LLVM codegen for a cleanup on cleanupScope here. + return mlir::success(); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCstEmitter.h b/clang/lib/CIR/CodeGen/CIRGenCstEmitter.h new file mode 100644 index 000000000000..5c9e545f227f --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenCstEmitter.h @@ -0,0 +1,153 @@ +//===--- CIRGenCstEmitter.h - CIR constant emission -------------*- C++ -*-===// +// +// 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 mlir::cir::ConstantOp +// and as initializers for global variables. +// +// Note: this is based on LLVM's codegen in ConstantEmitter.h, reusing this +// class interface makes it easier move forward with bringing CIR codegen +// to completion. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CODEGEN_CIRGEN_CONSTANTEMITTER_H +#define LLVM_CLANG_LIB_CODEGEN_CIRGEN_CONSTANTEMITTER_H + +#include "CIRGenFunction.h" +#include "CIRGenModule.h" + +namespace cir { + +class ConstantEmitter { +public: + CIRGenModule &CGM; + CIRGenFunction *const CGF; + +private: + bool Abstract = false; + + /// Whether non-abstract components of the emitter have been initialized. + bool InitializedNonAbstract = false; + + /// Whether the emitter has been finalized. + bool Finalized = false; + + /// Whether the constant-emission failed. + bool Failed = false; + + /// Whether we're in a constant context. + bool InConstantContext = false; + + /// The AST address space where this (non-abstract) initializer is going. + /// Used for generating appropriate placeholders. + clang::LangAS DestAddressSpace; + + llvm::SmallVector, 4> + PlaceholderAddresses; + +public: + ConstantEmitter(CIRGenModule &CGM, CIRGenFunction *CGF = nullptr) + : CGM(CGM), CGF(CGF) {} + + /// 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; + + ~ConstantEmitter(); + + /// Is the current emission context abstract? + bool isAbstract() const { return Abstract; } + + /// Try to emit the initiaizer of the given declaration as an abstract + /// constant. If this succeeds, the emission must be finalized. + mlir::Attribute tryEmitForInitializer(const VarDecl &D); + + void finalize(mlir::cir::GlobalOp global); + + // 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) { + return emitForMemory(CGM, C, T); + } + + // static llvm::Constant *emitNullForMemory(CodeGenModule &CGM, QualType T); + static mlir::Attribute emitForMemory(CIRGenModule &CGM, mlir::Attribute C, + clang::QualType T); + + /// Try to emit the initializer of the given declaration as an abstract + /// constant. + mlir::Attribute tryEmitAbstractForInitializer(const VarDecl &D); + + /// 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(const Expr *E, QualType T); + mlir::Attribute emitAbstract(SourceLocation loc, const APValue &value, + QualType T); + + // 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::TypedAttr tryEmitPrivate(const Expr *E, QualType T); + mlir::TypedAttr tryEmitPrivateForMemory(const Expr *E, QualType T); + + mlir::Attribute tryEmitPrivate(const APValue &value, QualType T); + mlir::Attribute tryEmitPrivateForMemory(const APValue &value, QualType T); + + mlir::Attribute tryEmitAbstract(const Expr *E, QualType destType); + mlir::Attribute tryEmitAbstractForMemory(const Expr *E, QualType destType); + + mlir::Attribute tryEmitAbstract(const APValue &value, QualType destType); + mlir::Attribute tryEmitAbstractForMemory(const APValue &value, + QualType destType); + +private: + void initializeNonAbstract(clang::LangAS destAS) { + assert(!InitializedNonAbstract); + InitializedNonAbstract = true; + DestAddressSpace = destAS; + } + mlir::Attribute markIfFailed(mlir::Attribute init) { + if (!init) + Failed = true; + return init; + } + + struct AbstractState { + bool OldValue; + size_t OldPlaceholdersSize; + }; + AbstractState pushAbstract() { + AbstractState saved = {Abstract, PlaceholderAddresses.size()}; + Abstract = true; + return saved; + } + mlir::Attribute validateAndPopAbstract(mlir::Attribute C, AbstractState save); +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenDecl.cpp b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp new file mode 100644 index 000000000000..20f774b171a6 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenDecl.cpp @@ -0,0 +1,973 @@ +//===--- CIRGenDecl.cpp - Emit CIR Code for declarations ------------------===// +// +// 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 "CIRDataLayout.h" +#include "CIRGenBuilder.h" +#include "CIRGenCstEmitter.h" +#include "CIRGenFunction.h" +#include "EHScopeStack.h" +#include "UnimplementedFeatureGuarding.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributeInterfaces.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/SymbolTable.h" + +#include "clang/AST/Decl.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/Support/ErrorHandling.h" +#include + +using namespace cir; +using namespace clang; + +CIRGenFunction::AutoVarEmission +CIRGenFunction::buildAutoVarAlloca(const VarDecl &D) { + QualType Ty = D.getType(); + // TODO: (|| Ty.getAddressSpace() == LangAS::opencl_private && + // getLangOpts().OpenCL)) + assert(!UnimplementedFeature::openCL()); + assert(!UnimplementedFeature::openMP()); + assert(Ty.getAddressSpace() == LangAS::Default); + assert(!Ty->isVariablyModifiedType() && "not implemented"); + assert(!getContext() + .getLangOpts() + .OpenMP && // !CGF.getLangOpts().OpenMPIRBuilder + "not implemented"); + assert(!D.hasAttr() && "not implemented"); + + auto loc = getLoc(D.getSourceRange()); + bool NRVO = + getContext().getLangOpts().ElideConstructors && D.isNRVOVariable(); + AutoVarEmission emission(D); + bool isEscapingByRef = D.isEscapingByref(); + emission.IsEscapingByRef = isEscapingByRef; + + CharUnits alignment = getContext().getDeclAlign(&D); + assert(!UnimplementedFeature::generateDebugInfo()); + assert(!UnimplementedFeature::cxxABI()); + + Address address = Address::invalid(); + Address allocaAddr = Address::invalid(); + Address openMPLocalAddr = Address::invalid(); + if (getLangOpts().OpenMP && openMPLocalAddr.isValid()) { + llvm_unreachable("NYI"); + } else if (Ty->isConstantSizeType()) { + // If this value is an array or struct with a statically determinable + // constant initializer, there are optimizations we can do. + // + // TODO: We should constant-evaluate the initializer of any variable, + // as long as it is initialized by a constant expression. Currently, + // isConstantInitializer produces wrong answers for structs with + // reference or bitfield members, and a few other cases, and checking + // for POD-ness protects us from some of these. + if (D.getInit() && (Ty->isArrayType() || Ty->isRecordType()) && + (D.isConstexpr() || + ((Ty.isPODType(getContext()) || + getContext().getBaseElementType(Ty)->isObjCObjectPointerType()) && + D.getInit()->isConstantInitializer(getContext(), false)))) { + + // If the variable's a const type, and it's neither an NRVO + // candidate nor a __block variable and has no mutable members, + // emit it as a global instead. + // Exception is if a variable is located in non-constant address space + // in OpenCL. + // TODO: deal with CGM.getCodeGenOpts().MergeAllConstants + // TODO: perhaps we don't need this at all at CIR since this can + // be done as part of lowering down to LLVM. + if ((!getContext().getLangOpts().OpenCL || + Ty.getAddressSpace() == LangAS::opencl_constant) && + (!NRVO && !D.isEscapingByref() && + CGM.isTypeConstant(Ty, /*ExcludeCtor=*/true, /*ExcludeDtor=*/false))) + assert(0 && "not implemented"); + + // Otherwise, tell the initialization code that we're in this case. + emission.IsConstantAggregate = true; + } + + // A normal fixed sized variable becomes an alloca in the entry block, + // unless: + // - it's an NRVO variable. + // - we are compiling OpenMP and it's an OpenMP local variable. + if (NRVO) { + // The named return value optimization: allocate this variable in the + // return slot, so that we can elide the copy when returning this + // variable (C++0x [class.copy]p34). + address = ReturnValue; + allocaAddr = ReturnValue; + + if (const RecordType *RecordTy = Ty->getAs()) { + const auto *RD = RecordTy->getDecl(); + const auto *CXXRD = dyn_cast(RD); + if ((CXXRD && !CXXRD->hasTrivialDestructor()) || + RD->isNonTrivialToPrimitiveDestroy()) { + // In LLVM: Create a flag that is used to indicate when the NRVO was + // applied to this variable. Set it to zero to indicate that NRVO was + // not applied. For now, use the same approach for CIRGen until we can + // be sure it's worth doing something more aggressive. + auto falseNVRO = builder.getFalse(loc); + Address NRVOFlag = CreateTempAlloca( + falseNVRO.getType(), CharUnits::One(), loc, "nrvo", + /*ArraySize=*/nullptr, &allocaAddr); + assert(builder.getInsertionBlock()); + builder.createStore(loc, falseNVRO, NRVOFlag); + + // Record the NRVO flag for this variable. + NRVOFlags[&D] = NRVOFlag.getPointer(); + emission.NRVOFlag = NRVOFlag.getPointer(); + } + } + } else { + if (isEscapingByRef) + llvm_unreachable("NYI"); + + mlir::Type allocaTy = getTypes().convertTypeForMem(Ty); + CharUnits allocaAlignment = alignment; + // Create the temp alloca and declare variable using it. + mlir::Value addrVal; + address = CreateTempAlloca(allocaTy, allocaAlignment, loc, D.getName(), + /*ArraySize=*/nullptr, &allocaAddr); + if (failed(declare(address, &D, Ty, getLoc(D.getSourceRange()), alignment, + addrVal))) { + CGM.emitError("Cannot declare variable"); + return emission; + } + // TODO: what about emitting lifetime markers for MSVC catch parameters? + // TODO: something like @llvm.lifetime.start/end here? revisit this later. + assert(!UnimplementedFeature::shouldEmitLifetimeMarkers()); + } + } else { // not openmp nor constant sized type + llvm_unreachable("NYI"); + } + + emission.Addr = address; + setAddrOfLocalVar(&D, emission.Addr); + 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(Init)) + if (CXXConstructorDecl *Constructor = Construct->getConstructor()) + if (Constructor->isTrivial() && Constructor->isDefaultConstructor() && + !Construct->requiresZeroInitialization()) + return true; + + return false; +} + +static void emitStoresForConstant(CIRGenModule &CGM, const VarDecl &D, + Address addr, bool isVolatile, + CIRGenBuilderTy &builder, + mlir::TypedAttr constant, bool IsAutoInit) { + auto Ty = constant.getType(); + cir::CIRDataLayout layout{CGM.getModule()}; + uint64_t ConstantSize = layout.getTypeAllocSize(Ty); + if (!ConstantSize) + return; + assert(!UnimplementedFeature::addAutoInitAnnotation()); + assert(!UnimplementedFeature::cirVectorType()); + assert(!UnimplementedFeature::shouldUseBZeroPlusStoresToInitialize()); + assert(!UnimplementedFeature::shouldUseMemSetToInitialize()); + assert(!UnimplementedFeature::shouldSplitConstantStore()); + assert(!UnimplementedFeature::shouldCreateMemCpyFromGlobal()); + // In CIR we want to emit a store for the whole thing, later lowering + // prepare to LLVM should unwrap this into the best policy (see asserts + // above). + // + // FIXME(cir): This is closer to memcpy behavior but less optimal, instead of + // copy from a global, we just create a cir.const out of it. + auto loc = CGM.getLoc(D.getSourceRange()); + builder.createStore(loc, builder.getConstant(loc, constant), addr); +} + +void CIRGenFunction::buildAutoVarInit(const AutoVarEmission &emission) { + assert(emission.Variable && "emission was not valid!"); + + const VarDecl &D = *emission.Variable; + QualType type = D.getType(); + + // If this local has an initializer, emit it now. + const Expr *Init = D.getInit(); + + // TODO: in LLVM codegen if we are at an unreachable point, the initializer + // isn't emitted unless it contains a label. What we want for CIR? + assert(builder.getInsertionBlock()); + + // Initialize the variable here if it doesn't have a initializer and it is a + // C struct that is non-trivial to initialize or an array containing such a + // struct. + if (!Init && type.isNonTrivialToPrimitiveDefaultInitialize() == + QualType::PDIK_Struct) { + assert(0 && "not implemented"); + return; + } + + const Address Loc = 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(!UnimplementedFeature::capturedByInit() && "NYI"); + + // Note: constexpr already initializes everything correctly. + LangOptions::TrivialAutoVarInitKind trivialAutoVarInit = + (D.isConstexpr() + ? LangOptions::TrivialAutoVarInitKind::Uninitialized + : (D.getAttr() + ? LangOptions::TrivialAutoVarInitKind::Uninitialized + : getContext().getLangOpts().getTrivialAutoVarInit())); + + auto initializeWhatIsTechnicallyUninitialized = [&](Address Loc) { + if (trivialAutoVarInit == + LangOptions::TrivialAutoVarInitKind::Uninitialized) + return; + + assert(0 && "unimplemented"); + }; + + if (isTrivialInitializer(Init)) + return initializeWhatIsTechnicallyUninitialized(Loc); + + 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 interesting 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 && !constant.isa() && + (trivialAutoVarInit != + LangOptions::TrivialAutoVarInitKind::Uninitialized)) { + llvm_unreachable("NYI"); + } + } + + if (!constant) { + initializeWhatIsTechnicallyUninitialized(Loc); + LValue lv = LValue::makeAddr(Loc, type, AlignmentSource::Decl); + buildExprAsInit(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. + auto addr = lv.getAddress().getPointer(); + assert(addr && "Should have an address"); + auto allocaOp = dyn_cast_or_null(addr.getDefiningOp()); + assert(allocaOp && "Address should come straight out of the alloca"); + + if (!allocaOp.use_empty()) + allocaOp.setInitAttr(mlir::UnitAttr::get(builder.getContext())); + return; + } + + // FIXME(cir): migrate most of this file to use mlir::TypedAttr directly. + auto typedConstant = constant.dyn_cast(); + assert(typedConstant && "expected typed attribute"); + if (!emission.IsConstantAggregate) { + // For simple scalar/complex initialization, store the value directly. + LValue lv = makeAddrLValue(Loc, type); + assert(Init && "expected initializer"); + auto initLoc = getLoc(Init->getSourceRange()); + lv.setNonGC(true); + return buildStoreThroughLValue( + RValue::get(builder.getConstant(initLoc, typedConstant)), lv); + } + + emitStoresForConstant(CGM, D, Loc, type.isVolatileQualified(), builder, + typedConstant, /*IsAutoInit=*/false); +} + +void CIRGenFunction::buildAutoVarCleanups(const AutoVarEmission &emission) { + assert(emission.Variable && "emission was not valid!"); + + // TODO: in LLVM codegen if we are at an unreachable point codgen + // is ignored. What we want for CIR? + assert(builder.getInsertionBlock()); + const VarDecl &D = *emission.Variable; + + // Check the type for a cleanup. + if (QualType::DestructionKind dtorKind = D.needsDestruction(getContext())) + buildAutoVarTypeCleanup(emission, dtorKind); + + // In GC mode, honor objc_precise_lifetime. + if (getContext().getLangOpts().getGC() != LangOptions::NonGC && + D.hasAttr()) + assert(0 && "not implemented"); + + // Handle the cleanup attribute. + if (const CleanupAttr *CA = D.getAttr()) + assert(0 && "not implemented"); + + // TODO: handle block variable +} + +/// 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::buildAutoVarDecl(const VarDecl &D) { + AutoVarEmission emission = buildAutoVarAlloca(D); + buildAutoVarInit(emission); + buildAutoVarCleanups(emission); +} + +void CIRGenFunction::buildVarDecl(const VarDecl &D) { + if (D.hasExternalStorage()) { + assert(0 && "should we just returns is there something to track?"); + // Don't emit it now, allow it to be emitted lazily on its first use. + return; + } + + // Some function-scope variable does not have static storage but still + // needs to be emitted like a static variable, e.g. a function-scope + // variable in constant address space in OpenCL. + if (D.getStorageDuration() != SD_Automatic) { + // Static sampler variables translated to function calls. + if (D.getType()->isSamplerT()) + return; + + auto Linkage = CGM.getCIRLinkageVarDefinition(&D, /*IsConstant=*/false); + + // FIXME: We need to force the emission/use of a guard variable for + // some variables even if we can constant-evaluate them because + // we can't guarantee every translation unit will constant-evaluate them. + + return buildStaticVarDecl(D, Linkage); + } + + if (D.getType().getAddressSpace() == LangAS::opencl_local) + llvm_unreachable("OpenCL and address space are NYI"); + + assert(D.hasLocalStorage()); + + CIRGenFunction::VarDeclContext varDeclCtx{*this, &D}; + return buildAutoVarDecl(D); +} + +static std::string getStaticDeclName(CIRGenModule &CGM, const VarDecl &D) { + if (CGM.getLangOpts().CPlusPlus) + return CGM.getMangledName(&D).str(); + + // If this isn't C++, we don't need a mangled name, just a pretty one. + assert(!D.isExternallyVisible() && "name shouldn't matter"); + std::string ContextName; + const DeclContext *DC = D.getDeclContext(); + if (auto *CD = dyn_cast(DC)) + DC = cast(CD->getNonClosureContext()); + if (const auto *FD = dyn_cast(DC)) + ContextName = std::string(CGM.getMangledName(FD)); + else if (const auto *BD = dyn_cast(DC)) + llvm_unreachable("block decl context for static var is NYI"); + else if (const auto *OMD = dyn_cast(DC)) + llvm_unreachable("ObjC decl context for static var is NYI"); + else + llvm_unreachable("Unknown context for static var decl"); + + ContextName += "." + D.getNameAsString(); + return ContextName; +} + +// TODO(cir): LLVM uses a Constant base class. Maybe CIR could leverage an +// interface for all constants? +mlir::cir::GlobalOp +CIRGenModule::getOrCreateStaticVarDecl(const VarDecl &D, + mlir::cir::GlobalLinkageKind Linkage) { + // In general, we don't always emit static var decls once before we reference + // them. It is possible to reference them before emitting the function that + // contains them, and it is possible to emit the containing function multiple + // times. + if (mlir::cir::GlobalOp ExistingGV = StaticLocalDeclMap[&D]) + return ExistingGV; + + QualType Ty = D.getType(); + assert(Ty->isConstantSizeType() && "VLAs can't be static"); + + // Use the label if the variable is renamed with the asm-label extension. + std::string Name; + if (D.hasAttr()) + llvm_unreachable("asm label is NYI"); + else + Name = getStaticDeclName(*this, D); + + mlir::Type LTy = getTypes().convertTypeForMem(Ty); + assert(!UnimplementedFeature::addressSpace()); + + // OpenCL variables in local address space and CUDA shared + // variables cannot have an initializer. + mlir::Attribute Init = nullptr; + if (Ty.getAddressSpace() == LangAS::opencl_local || + D.hasAttr() || D.hasAttr()) + llvm_unreachable("OpenCL & CUDA are NYI"); + else + Init = builder.getZeroInitAttr(getTypes().ConvertType(Ty)); + + mlir::cir::GlobalOp GV = builder.createVersionedGlobal( + getModule(), getLoc(D.getLocation()), Name, LTy, false, Linkage); + // TODO(cir): infer visibility from linkage in global op builder. + GV.setVisibility(getMLIRVisibilityFromCIRLinkage(Linkage)); + GV.setInitialValueAttr(Init); + GV.setAlignment(getASTContext().getDeclAlign(&D).getAsAlign().value()); + + if (supportsCOMDAT() && GV.isWeakForLinker()) + llvm_unreachable("COMDAT globals are NYI"); + + if (D.getTLSKind()) + llvm_unreachable("TLS mode is NYI"); + + setGVProperties(GV, &D); + + // Make sure the result is of the correct type. + assert(!UnimplementedFeature::addressSpace()); + + // Ensure that the static local gets initialized by making sure the parent + // function gets emitted eventually. + const Decl *DC = cast(D.getDeclContext()); + + // We can't name blocks or captured statements directly, so try to emit their + // parents. + if (isa(DC) || isa(DC)) { + DC = DC->getNonClosureContext(); + // FIXME: Ensure that global blocks get emitted. + if (!DC) + llvm_unreachable("address space is NYI"); + } + + GlobalDecl GD; + if (const auto *CD = dyn_cast(DC)) + llvm_unreachable("C++ constructors static var context is NYI"); + else if (const auto *DD = dyn_cast(DC)) + llvm_unreachable("C++ destructors static var context is NYI"); + else if (const auto *FD = dyn_cast(DC)) + GD = GlobalDecl(FD); + else { + // Don't do anything for Obj-C method decls or global closures. We should + // never defer them. + assert(isa(DC) && "unexpected parent code decl"); + } + if (GD.getDecl() && UnimplementedFeature::openMP()) { + // Disable emission of the parent function for the OpenMP device codegen. + llvm_unreachable("OpenMP is NYI"); + } + + return GV; +} + +/// Add the initializer for 'D' to the global variable that has already been +/// created for it. If the initializer has a different type than GV does, this +/// may free GV and return a different one. Otherwise it just returns GV. +mlir::cir::GlobalOp +CIRGenFunction::addInitializerToStaticVarDecl(const VarDecl &D, + mlir::cir::GlobalOp GV) { + ConstantEmitter emitter(*this); + mlir::TypedAttr Init = + emitter.tryEmitForInitializer(D).dyn_cast(); + assert(Init && "Expected typed attribute"); + + // If constant emission failed, then this should be a C++ static + // initializer. + if (!Init) { + if (!getLangOpts().CPlusPlus) + CGM.ErrorUnsupported(D.getInit(), "constant l-value expression"); + else if (D.hasFlexibleArrayInit(getContext())) + CGM.ErrorUnsupported(D.getInit(), "flexible array initializer"); + else { + // Since we have a static initializer, this global variable can't + // be constant. + GV.setConstant(false); + llvm_unreachable("C++ guarded init it NYI"); + } + return GV; + } + +#ifndef NDEBUG + CharUnits VarSize = CGM.getASTContext().getTypeSizeInChars(D.getType()) + + D.getFlexibleArrayInitChars(getContext()); + CharUnits CstSize = CharUnits::fromQuantity( + CGM.getDataLayout().getTypeAllocSize(Init.getType())); + assert(VarSize == CstSize && "Emitted constant has unexpected size"); +#endif + + // The initializer may differ in type from the global. Rewrite + // the global to match the initializer. (We have to do this + // because some types, like unions, can't be completely represented + // in the LLVM type system.) + if (GV.getSymType() != Init.getType()) { + llvm_unreachable("static decl initializer type mismatch is NYI"); + } + + bool NeedsDtor = + D.needsDestruction(getContext()) == QualType::DK_cxx_destructor; + + GV.setConstant( + CGM.isTypeConstant(D.getType(), /*ExcludeCtor=*/true, !NeedsDtor)); + GV.setInitialValueAttr(Init); + + emitter.finalize(GV); + + if (NeedsDtor) { + // We have a constant initializer, but a nontrivial destructor. We still + // need to perform a guarded "initialization" in order to register the + // destructor. + llvm_unreachable("C++ guarded init is NYI"); + } + + return GV; +} + +void CIRGenFunction::buildStaticVarDecl(const VarDecl &D, + mlir::cir::GlobalLinkageKind Linkage) { + // Check to see if we already have a global variable for this + // declaration. This can happen when double-emitting function + // bodies, e.g. with complete and base constructors. + auto globalOp = CGM.getOrCreateStaticVarDecl(D, Linkage); + // TODO(cir): we should have a way to represent global ops as values without + // having to emit a get global op. Sometimes these emissions are not used. + auto addr = getBuilder().createGetGlobal(globalOp); + CharUnits alignment = getContext().getDeclAlign(&D); + + // Store into LocalDeclMap before generating initializer to handle + // circular references. + mlir::Type elemTy = getTypes().convertTypeForMem(D.getType()); + setAddrOfLocalVar(&D, Address(addr, elemTy, alignment)); + + // We can't have a VLA here, but we can have a pointer to a VLA, + // even though that doesn't really make any sense. + // Make sure to evaluate VLA bounds now so that we have them for later. + if (D.getType()->isVariablyModifiedType()) + llvm_unreachable("VLAs are NYI"); + + // Save the type in case adding the initializer forces a type change. + mlir::Type expectedType = addr.getType(); + + auto var = globalOp; + + // CUDA's local and local static __shared__ variables should not + // have any non-empty initializers. This is ensured by Sema. + // Whatever initializer such variable may have when it gets here is + // a no-op and should not be emitted. + bool isCudaSharedVar = getLangOpts().CUDA && getLangOpts().CUDAIsDevice && + D.hasAttr(); + // If this value has an initializer, emit it. + if (D.getInit() && !isCudaSharedVar) + var = addInitializerToStaticVarDecl(D, var); + + var.setAlignment(alignment.getAsAlign().value()); + + if (D.hasAttr()) + llvm_unreachable("Global annotations are NYI"); + + if (auto *SA = D.getAttr()) + llvm_unreachable("CIR global BSS section attribute is NYI"); + if (auto *SA = D.getAttr()) + llvm_unreachable("CIR global Data section attribute is NYI"); + if (auto *SA = D.getAttr()) + llvm_unreachable("CIR global Rodata section attribute is NYI"); + if (auto *SA = D.getAttr()) + llvm_unreachable("CIR global Relro section attribute is NYI"); + + if (const SectionAttr *SA = D.getAttr()) + llvm_unreachable("CIR global object file section attribute is NYI"); + + if (D.hasAttr()) + llvm_unreachable("llvm.used metadata is NYI"); + else if (D.hasAttr()) + llvm_unreachable("llvm.compiler.used metadata is NYI"); + + // We may have to cast the constant because of the initializer + // mismatch above. + // + // FIXME: It is really dangerous to store this in the map; if anyone + // RAUW's the GV uses of this constant will be invalid. + // TODO(cir): its suppose to be possible that the initializer does not match + // the static var type. When this happens, there should be a cast here. + assert(var.getSymType() != expectedType && + "static var init type mismatch is NYI"); + CGM.setStaticLocalDeclAddress(&D, var); + + assert(!UnimplementedFeature::reportGlobalToASan()); + + // Emit global variable debug descriptor for static vars. + auto *DI = getDebugInfo(); + if (DI && CGM.getCodeGenOpts().hasReducedDebugInfo()) { + llvm_unreachable("Debug info is NYI"); + } +} + +void CIRGenFunction::buildNullabilityCheck(LValue LHS, mlir::Value RHS, + SourceLocation Loc) { + if (!SanOpts.has(SanitizerKind::NullabilityAssign)) + return; + + llvm_unreachable("NYI"); +} + +void CIRGenFunction::buildScalarInit(const Expr *init, mlir::Location loc, + LValue lvalue, bool capturedByInit) { + // TODO: this is where a lot of ObjC lifetime stuff would be done. + mlir::Value value = buildScalarExpr(init); + SourceLocRAIIObject Loc{*this, loc}; + buildStoreThroughLValue(RValue::get(value), lvalue); + return; +} + +void CIRGenFunction::buildExprAsInit(const Expr *init, const ValueDecl *D, + LValue lvalue, bool capturedByInit) { + SourceLocRAIIObject Loc{*this, getLoc(init->getSourceRange())}; + if (capturedByInit) + llvm_unreachable("NYI"); + + QualType type = D->getType(); + + if (type->isReferenceType()) { + RValue rvalue = buildReferenceBindingToExpr(init); + if (capturedByInit) + llvm_unreachable("NYI"); + buildStoreThroughLValue(rvalue, lvalue); + return; + } + switch (CIRGenFunction::getEvaluationKind(type)) { + case TEK_Scalar: + buildScalarInit(init, getLoc(D->getSourceRange()), lvalue); + return; + case TEK_Complex: { + assert(0 && "not implemented"); + return; + } + case TEK_Aggregate: + assert(!type->isAtomicType() && "NYI"); + AggValueSlot::Overlap_t Overlap = AggValueSlot::MayOverlap; + if (isa(D)) + Overlap = AggValueSlot::DoesNotOverlap; + else if (auto *FD = dyn_cast(D)) + assert(false && "Field decl NYI"); + else + assert(false && "Only VarDecl implemented so far"); + // TODO: how can we delay here if D is captured by its initializer? + buildAggExpr(init, + AggValueSlot::forLValue(lvalue, AggValueSlot::IsDestructed, + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased, Overlap)); + return; + } + llvm_unreachable("bad evaluation kind"); +} + +void CIRGenFunction::buildDecl(const Decl &D) { + switch (D.getKind()) { + case Decl::ImplicitConceptSpecialization: + case Decl::HLSLBuffer: + case Decl::TopLevelStmt: + llvm_unreachable("NYI"); + case Decl::BuiltinTemplate: + case Decl::TranslationUnit: + case Decl::ExternCContext: + case Decl::Namespace: + case Decl::UnresolvedUsingTypename: + case Decl::ClassTemplateSpecialization: + case Decl::ClassTemplatePartialSpecialization: + case Decl::VarTemplateSpecialization: + case Decl::VarTemplatePartialSpecialization: + case Decl::TemplateTypeParm: + case Decl::UnresolvedUsingValue: + case Decl::NonTypeTemplateParm: + case Decl::CXXDeductionGuide: + case Decl::CXXMethod: + case Decl::CXXConstructor: + case Decl::CXXDestructor: + case Decl::CXXConversion: + case Decl::Field: + case Decl::MSProperty: + case Decl::IndirectField: + case Decl::ObjCIvar: + case Decl::ObjCAtDefsField: + case Decl::ParmVar: + case Decl::ImplicitParam: + case Decl::ClassTemplate: + case Decl::VarTemplate: + case Decl::FunctionTemplate: + case Decl::TypeAliasTemplate: + case Decl::TemplateTemplateParm: + case Decl::ObjCMethod: + case Decl::ObjCCategory: + case Decl::ObjCProtocol: + case Decl::ObjCInterface: + case Decl::ObjCCategoryImpl: + case Decl::ObjCImplementation: + case Decl::ObjCProperty: + case Decl::ObjCCompatibleAlias: + case Decl::PragmaComment: + case Decl::PragmaDetectMismatch: + case Decl::AccessSpec: + case Decl::LinkageSpec: + case Decl::Export: + case Decl::ObjCPropertyImpl: + case Decl::FileScopeAsm: + case Decl::Friend: + case Decl::FriendTemplate: + case Decl::Block: + case Decl::Captured: + case Decl::ClassScopeFunctionSpecialization: + case Decl::UsingShadow: + case Decl::ConstructorUsingShadow: + case Decl::ObjCTypeParam: + case Decl::Binding: + case Decl::UnresolvedUsingIfExists: + llvm_unreachable("Declaration should not be in declstmts!"); + case Decl::Record: // struct/union/class X; + case Decl::CXXRecord: // struct/union/class X; [C++] + if (auto *DI = getDebugInfo()) + llvm_unreachable("NYI"); + return; + case Decl::Enum: // enum X; + if (auto *DI = getDebugInfo()) + llvm_unreachable("NYI"); + return; + case Decl::Function: // void X(); + case Decl::EnumConstant: // enum ? { X = ? } + case Decl::StaticAssert: // static_assert(X, ""); [C++0x] + case Decl::Label: // __label__ x; + case Decl::Import: + case Decl::MSGuid: // __declspec(uuid("...")) + case Decl::TemplateParamObject: + case Decl::OMPThreadPrivate: + case Decl::OMPAllocate: + case Decl::OMPCapturedExpr: + case Decl::OMPRequires: + case Decl::Empty: + case Decl::Concept: + case Decl::LifetimeExtendedTemporary: + case Decl::RequiresExprBody: + case Decl::UnnamedGlobalConstant: + // None of these decls require codegen support. + return; + + case Decl::NamespaceAlias: + assert(0 && "Not implemented"); + return; + case Decl::Using: // using X; [C++] + assert(0 && "Not implemented"); + return; + case Decl::UsingEnum: // using enum X; [C++] + assert(0 && "Not implemented"); + return; + case Decl::UsingPack: + assert(0 && "Not implemented"); + return; + case Decl::UsingDirective: // using namespace X; [C++] + assert(0 && "Not implemented"); + return; + case Decl::Var: + case Decl::Decomposition: { + const VarDecl &VD = cast(D); + assert(VD.isLocalVarDecl() && + "Should not see file-scope variables inside a function!"); + buildVarDecl(VD); + if (auto *DD = dyn_cast(&VD)) + assert(0 && "Not implemented"); + + // FIXME: add this + // if (auto *DD = dyn_cast(&VD)) + // for (auto *B : DD->bindings()) + // if (auto *HD = B->getHoldingVar()) + // EmitVarDecl(*HD); + return; + } + + case Decl::OMPDeclareReduction: + case Decl::OMPDeclareMapper: + assert(0 && "Not implemented"); + + case Decl::Typedef: // typedef int X; + case Decl::TypeAlias: { // using X = int; [C++0x] + assert(0 && "Not implemented"); + } + } +} + +namespace { +struct DestroyObject final : EHScopeStack::Cleanup { + DestroyObject(Address addr, QualType type, + CIRGenFunction::Destroyer *destroyer, bool useEHCleanupForArray) + : addr(addr), type(type), destroyer(destroyer), + useEHCleanupForArray(useEHCleanupForArray) {} + + Address addr; + QualType type; + CIRGenFunction::Destroyer *destroyer; + bool useEHCleanupForArray; + + void Emit(CIRGenFunction &CGF, Flags flags) override { + // Don't use an EH cleanup recursively from an EH cleanup. + [[maybe_unused]] bool useEHCleanupForArray = + flags.isForNormalCleanup() && this->useEHCleanupForArray; + + llvm_unreachable("NYI"); + // CGF.emitDestroy(addr, type, destroyer, useEHCleanupForArray); + } +}; + +template struct DestroyNRVOVariable : EHScopeStack::Cleanup { + DestroyNRVOVariable(Address addr, QualType type, mlir::Value NRVOFlag) + : NRVOFlag(NRVOFlag), Loc(addr), Ty(type) {} + + mlir::Value NRVOFlag; + Address Loc; + QualType Ty; + + void Emit(CIRGenFunction &CGF, Flags flags) override { + llvm_unreachable("NYI"); + } + + virtual ~DestroyNRVOVariable() = default; +}; + +struct DestroyNRVOVariableCXX final + : DestroyNRVOVariable { + DestroyNRVOVariableCXX(Address addr, QualType type, + const CXXDestructorDecl *Dtor, mlir::Value NRVOFlag) + : DestroyNRVOVariable(addr, type, NRVOFlag), + Dtor(Dtor) {} + + const CXXDestructorDecl *Dtor; + + void emitDestructorCall(CIRGenFunction &CGF) { llvm_unreachable("NYI"); } +}; + +struct DestroyNRVOVariableC final : DestroyNRVOVariable { + DestroyNRVOVariableC(Address addr, mlir::Value NRVOFlag, QualType Ty) + : DestroyNRVOVariable(addr, Ty, NRVOFlag) {} + + void emitDestructorCall(CIRGenFunction &CGF) { llvm_unreachable("NYI"); } +}; + +struct CallStackRestore final : EHScopeStack::Cleanup { + Address Stack; + CallStackRestore(Address Stack) : Stack(Stack) {} + bool isRedundantBeforeReturn() override { return true; } + void Emit(CIRGenFunction &CGF, Flags flags) override { + llvm_unreachable("NYI"); + } +}; + +struct ExtendGCLifetime final : EHScopeStack::Cleanup { + const VarDecl &Var; + ExtendGCLifetime(const VarDecl *var) : Var(*var) {} + + void Emit(CIRGenFunction &CGF, Flags flags) override { + llvm_unreachable("NYI"); + } +}; + +struct CallCleanupFunction final : EHScopeStack::Cleanup { + // FIXME: mlir::Value used as placeholder, check options before implementing + // Emit below. + mlir::Value CleanupFn; + const CIRGenFunctionInfo &FnInfo; + const VarDecl &Var; + + CallCleanupFunction(mlir::Value CleanupFn, const CIRGenFunctionInfo *Info, + const VarDecl *Var) + : CleanupFn(CleanupFn), FnInfo(*Info), Var(*Var) {} + + void Emit(CIRGenFunction &CGF, Flags flags) override { + llvm_unreachable("NYI"); + } +}; +} // end anonymous namespace + +void CIRGenFunction::pushDestroy(CleanupKind cleanupKind, Address addr, + QualType type, Destroyer *destroyer, + bool useEHCleanupForArray) { + pushFullExprCleanup(cleanupKind, addr, type, destroyer, + useEHCleanupForArray); +} + +CIRGenFunction::Destroyer * +CIRGenFunction::getDestroyer(QualType::DestructionKind kind) { + switch (kind) { + case QualType::DK_none: + llvm_unreachable("no destroyer for trivial dtor"); + case QualType::DK_cxx_destructor: + return destroyCXXObject; + case QualType::DK_objc_strong_lifetime: + case QualType::DK_objc_weak_lifetime: + case QualType::DK_nontrivial_c_struct: + llvm_unreachable("NYI"); + } + llvm_unreachable("Unknown DestructionKind"); +} + +/// Enter a destroy cleanup for the given local variable. +void CIRGenFunction::buildAutoVarTypeCleanup( + const CIRGenFunction::AutoVarEmission &emission, + QualType::DestructionKind dtorKind) { + assert(dtorKind != QualType::DK_none); + + // Note that for __block variables, we want to destroy the + // original stack object, not the possibly forwarded object. + Address addr = emission.getObjectAddress(*this); + + const VarDecl *var = emission.Variable; + QualType type = var->getType(); + + CleanupKind cleanupKind = NormalAndEHCleanup; + CIRGenFunction::Destroyer *destroyer = nullptr; + + switch (dtorKind) { + case QualType::DK_none: + llvm_unreachable("no cleanup for trivially-destructible variable"); + + case QualType::DK_cxx_destructor: + // If there's an NRVO flag on the emission, we need a different + // cleanup. + if (emission.NRVOFlag) { + assert(!type->isArrayType()); + CXXDestructorDecl *dtor = type->getAsCXXRecordDecl()->getDestructor(); + EHStack.pushCleanup(cleanupKind, addr, type, dtor, + emission.NRVOFlag); + return; + } + break; + + case QualType::DK_objc_strong_lifetime: + llvm_unreachable("NYI"); + break; + + case QualType::DK_objc_weak_lifetime: + break; + + case QualType::DK_nontrivial_c_struct: + llvm_unreachable("NYI"); + } + + // If we haven't chosen a more specific destroyer, use the default. + if (!destroyer) + destroyer = getDestroyer(dtorKind); + + // Use an EH cleanup in array destructors iff the destructor itself + // is being pushed as an EH cleanup. + bool useEHCleanup = (cleanupKind & EHCleanup); + EHStack.pushCleanup(cleanupKind, addr, type, destroyer, + useEHCleanup); +} + +/// Push the standard destructor for the given type as an EH-only cleanup. +void CIRGenFunction::pushEHDestroy(QualType::DestructionKind dtorKind, + Address addr, QualType type) { + assert(dtorKind && "cannot push destructor for trivial type"); + assert(needsEHCleanup(dtorKind)); + + pushDestroy(EHCleanup, addr, type, getDestroyer(dtorKind), true); +} \ No newline at end of file diff --git a/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp new file mode 100644 index 000000000000..3d8c72dd7f5e --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp @@ -0,0 +1,86 @@ +//===--- CIRGenDeclCXX.cpp - Build CIR Code for C++ declarations ----------===// +// +// 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 dealing with code generation of C++ declarations +// +//===----------------------------------------------------------------------===// + +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "TargetInfo.h" +#include "clang/AST/Attr.h" +#include "clang/Basic/LangOptions.h" + +using namespace clang; +using namespace mlir::cir; +using namespace cir; + +void CIRGenModule::buildCXXGlobalInitFunc() { + while (!CXXGlobalInits.empty() && !CXXGlobalInits.back()) + CXXGlobalInits.pop_back(); + + if (CXXGlobalInits.empty()) // TODO(cir): && + // PrioritizedCXXGlobalInits.empty()) + return; + + assert(0 && "NYE"); +} + +void CIRGenModule::buildGlobalVarDeclInit(const VarDecl *D, + mlir::cir::GlobalOp Addr, + bool PerformInit) { + // According to E.2.3.1 in CUDA-7.5 Programming guide: __device__, + // __constant__ and __shared__ variables defined in namespace scope, + // that are of class type, cannot have a non-empty constructor. All + // the checks have been done in Sema by now. Whatever initializers + // are allowed are empty and we just need to ignore them here. + if (getLangOpts().CUDAIsDevice && !getLangOpts().GPUAllowDeviceInit && + (D->hasAttr() || D->hasAttr() || + D->hasAttr())) + return; + + assert(!getLangOpts().OpenMP && "OpenMP global var init not implemented"); + + // Check if we've already initialized this decl. + auto I = DelayedCXXInitPosition.find(D); + if (I != DelayedCXXInitPosition.end() && I->second == ~0U) + return; + + if (PerformInit) { + QualType T = D->getType(); + + // TODO: handle address space + // The address space of a static local variable (DeclPtr) may be different + // from the address space of the "this" argument of the constructor. In that + // case, we need an addrspacecast before calling the constructor. + // + // struct StructWithCtor { + // __device__ StructWithCtor() {...} + // }; + // __device__ void foo() { + // __shared__ StructWithCtor s; + // ... + // } + // + // For example, in the above CUDA code, the static local variable s has a + // "shared" address space qualifier, but the constructor of StructWithCtor + // expects "this" in the "generic" address space. + assert(!UnimplementedFeature::addressSpace()); + + if (!T->isReferenceType()) { + bool NeedsDtor = + D->needsDestruction(getASTContext()) == QualType::DK_cxx_destructor; + assert(!isTypeConstant(D->getType(), true, !NeedsDtor) && + "invaraint-typed initialization NYI"); + + if (PerformInit || NeedsDtor) + codegenGlobalInitCxxStructor(D, Addr, PerformInit, NeedsDtor); + return; + } + } +} diff --git a/clang/lib/CIR/CodeGen/CIRGenException.cpp b/clang/lib/CIR/CodeGen/CIRGenException.cpp new file mode 100644 index 000000000000..bc20ec9839fa --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenException.cpp @@ -0,0 +1,454 @@ +//===--- CIRGenException.cpp - Emit CIR Code for C++ exceptions -*- C++ -*-===// +// +// 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 dealing with C++ exception related code generation. +// +//===----------------------------------------------------------------------===// + +#include "CIRDataLayout.h" +#include "CIRGenCXXABI.h" +#include "CIRGenCleanup.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/StmtVisitor.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/Support/ErrorHandling.h" +#include + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Value.h" + +using namespace cir; +using namespace clang; + +const EHPersonality EHPersonality::GNU_C = {"__gcc_personality_v0", nullptr}; +const EHPersonality EHPersonality::GNU_C_SJLJ = {"__gcc_personality_sj0", + nullptr}; +const EHPersonality EHPersonality::GNU_C_SEH = {"__gcc_personality_seh0", + nullptr}; +const EHPersonality EHPersonality::NeXT_ObjC = {"__objc_personality_v0", + nullptr}; +const EHPersonality EHPersonality::GNU_CPlusPlus = {"__gxx_personality_v0", + nullptr}; +const EHPersonality EHPersonality::GNU_CPlusPlus_SJLJ = { + "__gxx_personality_sj0", nullptr}; +const EHPersonality EHPersonality::GNU_CPlusPlus_SEH = { + "__gxx_personality_seh0", nullptr}; +const EHPersonality EHPersonality::GNU_ObjC = {"__gnu_objc_personality_v0", + "objc_exception_throw"}; +const EHPersonality EHPersonality::GNU_ObjC_SJLJ = { + "__gnu_objc_personality_sj0", "objc_exception_throw"}; +const EHPersonality EHPersonality::GNU_ObjC_SEH = { + "__gnu_objc_personality_seh0", "objc_exception_throw"}; +const EHPersonality EHPersonality::GNU_ObjCXX = { + "__gnustep_objcxx_personality_v0", nullptr}; +const EHPersonality EHPersonality::GNUstep_ObjC = { + "__gnustep_objc_personality_v0", nullptr}; +const EHPersonality EHPersonality::MSVC_except_handler = {"_except_handler3", + nullptr}; +const EHPersonality EHPersonality::MSVC_C_specific_handler = { + "__C_specific_handler", nullptr}; +const EHPersonality EHPersonality::MSVC_CxxFrameHandler3 = { + "__CxxFrameHandler3", nullptr}; +const EHPersonality EHPersonality::GNU_Wasm_CPlusPlus = { + "__gxx_wasm_personality_v0", nullptr}; +const EHPersonality EHPersonality::XL_CPlusPlus = {"__xlcxx_personality_v1", + nullptr}; + +static const EHPersonality &getCPersonality(const TargetInfo &Target, + const LangOptions &L) { + const llvm::Triple &T = Target.getTriple(); + if (T.isWindowsMSVCEnvironment()) + return EHPersonality::MSVC_CxxFrameHandler3; + if (L.hasSjLjExceptions()) + return EHPersonality::GNU_C_SJLJ; + if (L.hasDWARFExceptions()) + return EHPersonality::GNU_C; + if (L.hasSEHExceptions()) + return EHPersonality::GNU_C_SEH; + return EHPersonality::GNU_C; +} + +static const EHPersonality &getObjCPersonality(const TargetInfo &Target, + const LangOptions &L) { + const llvm::Triple &T = Target.getTriple(); + if (T.isWindowsMSVCEnvironment()) + return EHPersonality::MSVC_CxxFrameHandler3; + + switch (L.ObjCRuntime.getKind()) { + case ObjCRuntime::FragileMacOSX: + return getCPersonality(Target, L); + case ObjCRuntime::MacOSX: + case ObjCRuntime::iOS: + case ObjCRuntime::WatchOS: + return EHPersonality::NeXT_ObjC; + case ObjCRuntime::GNUstep: + if (L.ObjCRuntime.getVersion() >= VersionTuple(1, 7)) + return EHPersonality::GNUstep_ObjC; + [[fallthrough]]; + case ObjCRuntime::GCC: + case ObjCRuntime::ObjFW: + if (L.hasSjLjExceptions()) + return EHPersonality::GNU_ObjC_SJLJ; + if (L.hasSEHExceptions()) + return EHPersonality::GNU_ObjC_SEH; + return EHPersonality::GNU_ObjC; + } + llvm_unreachable("bad runtime kind"); +} + +static const EHPersonality &getCXXPersonality(const TargetInfo &Target, + const LangOptions &L) { + const llvm::Triple &T = Target.getTriple(); + if (T.isWindowsMSVCEnvironment()) + return EHPersonality::MSVC_CxxFrameHandler3; + if (T.isOSAIX()) + return EHPersonality::XL_CPlusPlus; + if (L.hasSjLjExceptions()) + return EHPersonality::GNU_CPlusPlus_SJLJ; + if (L.hasDWARFExceptions()) + return EHPersonality::GNU_CPlusPlus; + if (L.hasSEHExceptions()) + return EHPersonality::GNU_CPlusPlus_SEH; + if (L.hasWasmExceptions()) + return EHPersonality::GNU_Wasm_CPlusPlus; + return EHPersonality::GNU_CPlusPlus; +} + +/// Determines the personality function to use when both C++ +/// and Objective-C exceptions are being caught. +static const EHPersonality &getObjCXXPersonality(const TargetInfo &Target, + const LangOptions &L) { + if (Target.getTriple().isWindowsMSVCEnvironment()) + return EHPersonality::MSVC_CxxFrameHandler3; + + switch (L.ObjCRuntime.getKind()) { + // In the fragile ABI, just use C++ exception handling and hope + // they're not doing crazy exception mixing. + case ObjCRuntime::FragileMacOSX: + return getCXXPersonality(Target, L); + + // The ObjC personality defers to the C++ personality for non-ObjC + // handlers. Unlike the C++ case, we use the same personality + // function on targets using (backend-driven) SJLJ EH. + case ObjCRuntime::MacOSX: + case ObjCRuntime::iOS: + case ObjCRuntime::WatchOS: + return getObjCPersonality(Target, L); + + case ObjCRuntime::GNUstep: + return EHPersonality::GNU_ObjCXX; + + // The GCC runtime's personality function inherently doesn't support + // mixed EH. Use the ObjC personality just to avoid returning null. + case ObjCRuntime::GCC: + case ObjCRuntime::ObjFW: + return getObjCPersonality(Target, L); + } + llvm_unreachable("bad runtime kind"); +} + +static const EHPersonality &getSEHPersonalityMSVC(const llvm::Triple &T) { + if (T.getArch() == llvm::Triple::x86) + return EHPersonality::MSVC_except_handler; + return EHPersonality::MSVC_C_specific_handler; +} + +const EHPersonality &EHPersonality::get(CIRGenModule &CGM, + const FunctionDecl *FD) { + const llvm::Triple &T = CGM.getTarget().getTriple(); + const LangOptions &L = CGM.getLangOpts(); + const TargetInfo &Target = CGM.getTarget(); + + // Functions using SEH get an SEH personality. + if (FD && FD->usesSEHTry()) + return getSEHPersonalityMSVC(T); + + if (L.ObjC) + return L.CPlusPlus ? getObjCXXPersonality(Target, L) + : getObjCPersonality(Target, L); + return L.CPlusPlus ? getCXXPersonality(Target, L) + : getCPersonality(Target, L); +} + +const EHPersonality &EHPersonality::get(CIRGenFunction &CGF) { + const auto *FD = CGF.CurCodeDecl; + // For outlined finallys and filters, use the SEH personality in case they + // contain more SEH. This mostly only affects finallys. Filters could + // hypothetically use gnu statement expressions to sneak in nested SEH. + FD = FD ? FD : CGF.CurSEHParent.getDecl(); + return get(CGF.CGM, dyn_cast_or_null(FD)); +} + +void CIRGenFunction::buildCXXThrowExpr(const CXXThrowExpr *E) { + if (const Expr *SubExpr = E->getSubExpr()) { + QualType ThrowType = SubExpr->getType(); + if (ThrowType->isObjCObjectPointerType()) { + llvm_unreachable("NYI"); + } else { + CGM.getCXXABI().buildThrow(*this, E); + } + } else { + CGM.getCXXABI().buildRethrow(*this, /*isNoReturn=*/true); + } + + // In LLVM codegen the expression emitters expect to leave this + // path by starting a new basic block. We do not need that in CIR. +} + +namespace { +/// A cleanup to free the exception object if its initialization +/// throws. +struct FreeException final : EHScopeStack::Cleanup { + mlir::Value exn; + FreeException(mlir::Value exn) : exn(exn) {} + void Emit(CIRGenFunction &CGF, Flags flags) override { + llvm_unreachable("call to cxa_free or equivalent op NYI"); + } +}; +} // end anonymous namespace + +// Emits an exception expression into the given location. This +// differs from buildAnyExprToMem only in that, if a final copy-ctor +// call is required, an exception within that copy ctor causes +// std::terminate to be invoked. +void CIRGenFunction::buildAnyExprToExn(const Expr *e, Address addr) { + // Make sure the exception object is cleaned up if there's an + // exception during initialization. + pushFullExprCleanup(EHCleanup, addr.getPointer()); + EHScopeStack::stable_iterator cleanup = EHStack.stable_begin(); + + // __cxa_allocate_exception returns a void*; we need to cast this + // to the appropriate type for the object. + auto ty = convertTypeForMem(e->getType()); + Address typedAddr = addr.withElementType(ty); + + // From LLVM's codegen: + // FIXME: this isn't quite right! If there's a final unelided call + // to a copy constructor, then according to [except.terminate]p1 we + // must call std::terminate() if that constructor throws, because + // technically that copy occurs after the exception expression is + // evaluated but before the exception is caught. But the best way + // to handle that is to teach EmitAggExpr to do the final copy + // differently if it can't be elided. + buildAnyExprToMem(e, typedAddr, e->getType().getQualifiers(), + /*IsInit*/ true); + + // Deactivate the cleanup block. + auto op = typedAddr.getPointer().getDefiningOp(); + assert(op && + "expected valid Operation *, block arguments are not meaningful here"); + DeactivateCleanupBlock(cleanup, op); +} + +mlir::LogicalResult CIRGenFunction::buildCXXTryStmt(const CXXTryStmt &S) { + auto tryLoc = getLoc(S.getBeginLoc()); + auto numHandlers = S.getNumHandlers(); + + // FIXME(cir): create scope, and add catchOp to the lastest possible position + // inside the cleanup block. + + // Create the skeleton for the catch statements. + auto catchOp = builder.create( + tryLoc, // FIXME(cir): we can do better source location here. + [&](mlir::OpBuilder &b, mlir::Location loc, + mlir::OperationState &result) { + mlir::OpBuilder::InsertionGuard guard(b); + for (int i = 0, e = numHandlers; i != e; ++i) { + auto *r = result.addRegion(); + builder.createBlock(r); + } + }); + + enterCXXTryStmt(S, catchOp); + if (buildStmt(S.getTryBlock(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + exitCXXTryStmt(S); + return mlir::success(); +} + +/// Emit the structure of the dispatch block for the given catch scope. +/// It is an invariant that the dispatch block already exists. +static void buildCatchDispatchBlock(CIRGenFunction &CGF, + EHCatchScope &catchScope) { + if (EHPersonality::get(CGF).isWasmPersonality()) + llvm_unreachable("NYI"); + if (EHPersonality::get(CGF).usesFuncletPads()) + llvm_unreachable("NYI"); + + auto *dispatchBlock = catchScope.getCachedEHDispatchBlock(); + assert(dispatchBlock); + + // If there's only a single catch-all, getEHDispatchBlock returned + // that catch-all as the dispatch block. + if (catchScope.getNumHandlers() == 1 && + catchScope.getHandler(0).isCatchAll()) { + llvm_unreachable("NYI"); // Remove when adding testcase. + assert(dispatchBlock == catchScope.getHandler(0).Block); + return; + } + + llvm_unreachable("NYI"); +} + +void CIRGenFunction::enterCXXTryStmt(const CXXTryStmt &S, + mlir::cir::CatchOp catchOp, + bool IsFnTryBlock) { + unsigned NumHandlers = S.getNumHandlers(); + EHCatchScope *CatchScope = EHStack.pushCatch(NumHandlers); + for (unsigned I = 0; I != NumHandlers; ++I) { + const CXXCatchStmt *C = S.getHandler(I); + + // FIXME: hook the CIR block for the right catch region here. + mlir::Block *Handler = &catchOp.getRegion(I).getBlocks().front(); + if (C->getExceptionDecl()) { + // FIXME: Dropping the reference type on the type into makes it + // impossible to correctly implement catch-by-reference + // semantics for pointers. Unfortunately, this is what all + // existing compilers do, and it's not clear that the standard + // personality routine is capable of doing this right. See C++ DR 388 : + // http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#388 + Qualifiers CaughtTypeQuals; + QualType CaughtType = CGM.getASTContext().getUnqualifiedArrayType( + C->getCaughtType().getNonReferenceType(), CaughtTypeQuals); + + CatchTypeInfo TypeInfo{nullptr, 0}; + if (CaughtType->isObjCObjectPointerType()) + llvm_unreachable("NYI"); + else + TypeInfo = CGM.getCXXABI().getAddrOfCXXCatchHandlerType( + getLoc(S.getSourceRange()), CaughtType, C->getCaughtType()); + CatchScope->setHandler(I, TypeInfo, Handler); + } else { + // No exception decl indicates '...', a catch-all. + llvm_unreachable("NYI"); + } + } +} + +void CIRGenFunction::exitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock) { + unsigned NumHandlers = S.getNumHandlers(); + EHCatchScope &CatchScope = cast(*EHStack.begin()); + assert(CatchScope.getNumHandlers() == NumHandlers); + + // If the catch was not required, bail out now. + if (!CatchScope.hasEHBranches()) { + llvm_unreachable("NYI"); + CatchScope.clearHandlerBlocks(); + EHStack.popCatch(); + return; + } + + // Emit the structure of the EH dispatch for this catch. + buildCatchDispatchBlock(*this, CatchScope); + llvm_unreachable("NYI"); +} + +/// Check whether this is a non-EH scope, i.e. a scope which doesn't +/// affect exception handling. Currently, the only non-EH scopes are +/// normal-only cleanup scopes. +static bool isNonEHScope(const EHScope &S) { + switch (S.getKind()) { + case EHScope::Cleanup: + return !cast(S).isEHCleanup(); + case EHScope::Filter: + case EHScope::Catch: + case EHScope::Terminate: + return false; + } + + llvm_unreachable("Invalid EHScope Kind!"); +} + +mlir::Block *CIRGenFunction::buildLandingPad() { + assert(EHStack.requiresLandingPad()); + assert(!CGM.getLangOpts().IgnoreExceptions && + "LandingPad should not be emitted when -fignore-exceptions are in " + "effect."); + EHScope &innermostEHScope = *EHStack.find(EHStack.getInnermostEHScope()); + switch (innermostEHScope.getKind()) { + case EHScope::Terminate: + llvm_unreachable("NYI"); + + case EHScope::Catch: + case EHScope::Cleanup: + case EHScope::Filter: + if (auto *lpad = innermostEHScope.getCachedLandingPad()) + return lpad; + } + + { + // Save the current CIR generation state. + mlir::OpBuilder::InsertionGuard guard(builder); + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + // FIXME(cir): handle CIR relevant landing pad bits, there's no good + // way to assert here right now and leaving one in break important + // testcases. Work to fill this in is coming soon. + } + + return nullptr; +} + +mlir::Block *CIRGenFunction::getInvokeDestImpl() { + assert(EHStack.requiresLandingPad()); + assert(!EHStack.empty()); + + // If exceptions are disabled/ignored and SEH is not in use, then there is no + // invoke destination. SEH "works" even if exceptions are off. In practice, + // this means that C++ destructors and other EH cleanups don't run, which is + // consistent with MSVC's behavior, except in the presence of -EHa + const LangOptions &LO = CGM.getLangOpts(); + if (!LO.Exceptions || LO.IgnoreExceptions) { + if (!LO.Borland && !LO.MicrosoftExt) + return nullptr; + if (!currentFunctionUsesSEHTry()) + return nullptr; + } + + // CUDA device code doesn't have exceptions. + if (LO.CUDA && LO.CUDAIsDevice) + return nullptr; + + // Check the innermost scope for a cached landing pad. If this is + // a non-EH cleanup, we'll check enclosing scopes in EmitLandingPad. + auto *LP = EHStack.begin()->getCachedLandingPad(); + if (LP) + return LP; + + const EHPersonality &Personality = EHPersonality::get(*this); + + // FIXME(cir): add personality function + // if (!CurFn->hasPersonalityFn()) + // CurFn->setPersonalityFn(getOpaquePersonalityFn(CGM, Personality)); + + if (Personality.usesFuncletPads()) { + // We don't need separate landing pads in the funclet model. + llvm_unreachable("NYI"); + } else { + // Build the landing pad for this scope. + LP = buildLandingPad(); + } + + // FIXME(cir): this breaks important testcases, fix is coming soon. + // assert(LP); + + // Cache the landing pad on the innermost scope. If this is a + // non-EH scope, cache the landing pad on the enclosing scope, too. + for (EHScopeStack::iterator ir = EHStack.begin(); true; ++ir) { + ir->setCachedLandingPad(LP); + if (!isNonEHScope(*ir)) + break; + } + + return LP; +} \ No newline at end of file diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp new file mode 100644 index 000000000000..0765944d62e1 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp @@ -0,0 +1,2776 @@ +//===--- CIRGenExpr.cpp - Emit LLVM Code from Expressions -----------------===// +// +// 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 "CIRGenBuilder.h" +#include "CIRGenCXXABI.h" +#include "CIRGenCall.h" +#include "CIRGenCstEmitter.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "CIRGenValue.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/GlobalDecl.h" +#include "clang/Basic/Builtins.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/ADT/StringExtras.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/Operation.h" +#include "mlir/IR/Value.h" + +using namespace cir; +using namespace clang; +using namespace mlir::cir; + +static mlir::cir::FuncOp buildFunctionDeclPointer(CIRGenModule &CGM, + GlobalDecl GD) { + const auto *FD = cast(GD.getDecl()); + + if (FD->hasAttr()) { + mlir::Operation *aliasee = CGM.getWeakRefReference(FD); + return dyn_cast(aliasee); + } + + auto V = CGM.GetAddrOfFunction(GD); + + return V; +} + +static Address buildPreserveStructAccess(CIRGenFunction &CGF, LValue base, + Address addr, const FieldDecl *field) { + llvm_unreachable("NYI"); +} + +/// Get the address of a zero-sized field within a record. The resulting address +/// doesn't necessarily have the right type. +static Address buildAddrOfFieldStorage(CIRGenFunction &CGF, Address Base, + const FieldDecl *field, + llvm::StringRef fieldName, + unsigned fieldIndex) { + if (field->isZeroSize(CGF.getContext())) + llvm_unreachable("NYI"); + + auto loc = CGF.getLoc(field->getLocation()); + + auto fieldType = CGF.convertType(field->getType()); + auto fieldPtr = + mlir::cir::PointerType::get(CGF.getBuilder().getContext(), fieldType); + // For most cases fieldName is the same as field->getName() but for lambdas, + // which do not currently carry the name, so it can be passed down from the + // CaptureStmt. + auto memberAddr = CGF.getBuilder().createGetMember( + loc, fieldPtr, Base.getPointer(), fieldName, fieldIndex); + + // TODO: We could get the alignment from the CIRGenRecordLayout, but given the + // member name based lookup of the member here we probably shouldn't be. We'll + // have to consider this later. + auto addr = Address(memberAddr, CharUnits::One()); + return addr; +} + +static bool hasAnyVptr(const QualType Type, const ASTContext &Context) { + const auto *RD = Type.getTypePtr()->getAsCXXRecordDecl(); + if (!RD) + return false; + + if (RD->isDynamicClass()) + return true; + + for (const auto &Base : RD->bases()) + if (hasAnyVptr(Base.getType(), Context)) + return true; + + for (const FieldDecl *Field : RD->fields()) + if (hasAnyVptr(Field->getType(), Context)) + return true; + + return false; +} + +static Address buildPointerWithAlignment(const Expr *E, + LValueBaseInfo *BaseInfo, + KnownNonNull_t IsKnownNonNull, + CIRGenFunction &CGF) { + // We allow this with ObjC object pointers because of fragile ABIs. + assert(E->getType()->isPointerType() || + E->getType()->isObjCObjectPointerType()); + E = E->IgnoreParens(); + + // Casts: + if (const CastExpr *CE = dyn_cast(E)) { + if (const auto *ECE = dyn_cast(CE)) + CGF.CGM.buildExplicitCastExprType(ECE, &CGF); + + switch (CE->getCastKind()) { + default: { + llvm::errs() << CE->getCastKindName() << "\n"; + assert(0 && "not implemented"); + } + // Non-converting casts (but not C's implicit conversion from void*). + case CK_BitCast: + case CK_NoOp: + case CK_AddressSpaceConversion: + if (auto PtrTy = + CE->getSubExpr()->getType()->getAs()) { + if (PtrTy->getPointeeType()->isVoidType()) + break; + assert(!UnimplementedFeature::tbaa()); + + LValueBaseInfo InnerBaseInfo; + Address Addr = CGF.buildPointerWithAlignment( + CE->getSubExpr(), &InnerBaseInfo, IsKnownNonNull); + if (BaseInfo) + *BaseInfo = InnerBaseInfo; + + if (isa(CE)) { + assert(!UnimplementedFeature::tbaa()); + LValueBaseInfo TargetTypeBaseInfo; + + CharUnits Align = CGF.CGM.getNaturalPointeeTypeAlignment( + E->getType(), &TargetTypeBaseInfo); + + // If the source l-value is opaque, honor the alignment of the + // casted-to type. + if (InnerBaseInfo.getAlignmentSource() != AlignmentSource::Decl) { + if (BaseInfo) + BaseInfo->mergeForCast(TargetTypeBaseInfo); + Addr = Address(Addr.getPointer(), Addr.getElementType(), Align, + IsKnownNonNull); + } + } + + if (CGF.SanOpts.has(SanitizerKind::CFIUnrelatedCast) && + CE->getCastKind() == CK_BitCast) { + if (auto PT = E->getType()->getAs()) + llvm_unreachable("NYI"); + } + + auto ElemTy = + CGF.getTypes().convertTypeForMem(E->getType()->getPointeeType()); + Addr = CGF.getBuilder().createElementBitCast( + CGF.getLoc(E->getSourceRange()), Addr, ElemTy); + if (CE->getCastKind() == CK_AddressSpaceConversion) { + assert(!UnimplementedFeature::addressSpace()); + llvm_unreachable("NYI"); + } + return Addr; + } + break; + + // Nothing to do here... + case CK_LValueToRValue: + break; + + // Array-to-pointer decay. TODO(cir): BaseInfo and TBAAInfo. + case CK_ArrayToPointerDecay: + return CGF.buildArrayToPointerDecay(CE->getSubExpr()); + + case CK_UncheckedDerivedToBase: + case CK_DerivedToBase: { + // TODO: Support accesses to members of base classes in TBAA. For now, we + // conservatively pretend that the complete object is of the base class + // type. + assert(!UnimplementedFeature::tbaa()); + Address Addr = CGF.buildPointerWithAlignment(CE->getSubExpr(), BaseInfo); + auto Derived = CE->getSubExpr()->getType()->getPointeeCXXRecordDecl(); + return CGF.getAddressOfBaseClass( + Addr, Derived, CE->path_begin(), CE->path_end(), + CGF.shouldNullCheckClassCastValue(CE), CE->getExprLoc()); + } + } + } + + // Unary &. + if (const UnaryOperator *UO = dyn_cast(E)) { + // TODO(cir): maybe we should use cir.unary for pointers here instead. + if (UO->getOpcode() == UO_AddrOf) { + LValue LV = CGF.buildLValue(UO->getSubExpr()); + if (BaseInfo) + *BaseInfo = LV.getBaseInfo(); + assert(!UnimplementedFeature::tbaa()); + return LV.getAddress(); + } + } + + // TODO: conditional operators, comma. + // Otherwise, use the alignment of the type. + CharUnits Align = + CGF.CGM.getNaturalPointeeTypeAlignment(E->getType(), BaseInfo); + return Address(CGF.buildScalarExpr(E), Align); +} + +/// Helper method to check if the underlying ABI is AAPCS +static bool isAAPCS(const TargetInfo &TargetInfo) { + return TargetInfo.getABI().startswith("aapcs"); +} + +Address CIRGenFunction::getAddrOfBitFieldStorage(LValue base, + const FieldDecl *field, + unsigned index, + unsigned size) { + if (index == 0) + return base.getAddress(); + + auto loc = getLoc(field->getLocation()); + auto fieldType = builder.getUIntNTy(size); + + auto fieldPtr = + mlir::cir::PointerType::get(getBuilder().getContext(), fieldType); + auto sea = getBuilder().createGetMember( + loc, fieldPtr, base.getPointer(), field->getName(), index); + + return Address(sea, CharUnits::One()); +} + +static bool useVolatileForBitField(const CIRGenModule &cgm, LValue base, + const CIRGenBitFieldInfo &info, + const FieldDecl *field) { + return isAAPCS(cgm.getTarget()) && cgm.getCodeGenOpts().AAPCSBitfieldWidth && + info.VolatileStorageSize != 0 && + field->getType() + .withCVRQualifiers(base.getVRQualifiers()) + .isVolatileQualified(); +} + +LValue CIRGenFunction::buildLValueForBitField(LValue base, + const FieldDecl *field) { + + LValueBaseInfo BaseInfo = base.getBaseInfo(); + const RecordDecl *rec = field->getParent(); + auto &layout = CGM.getTypes().getCIRGenRecordLayout(field->getParent()); + auto &info = layout.getBitFieldInfo(field); + auto useVolatile = useVolatileForBitField(CGM, base, info, field); + unsigned Idx = layout.getCIRFieldNo(field); + + if (useVolatile || + (IsInPreservedAIRegion || + (getDebugInfo() && rec->hasAttr()))) { + llvm_unreachable("NYI"); + } + + const unsigned SS = useVolatile ? info.VolatileStorageSize : info.StorageSize; + Address Addr = getAddrOfBitFieldStorage(base, field, Idx, SS); + + // Get the access type. + mlir::Type FieldIntTy = builder.getUIntNTy(SS); + + auto loc = getLoc(field->getLocation()); + if (Addr.getElementType() != FieldIntTy) + Addr = builder.createElementBitCast(loc, Addr, FieldIntTy); + + QualType fieldType = + field->getType().withCVRQualifiers(base.getVRQualifiers()); + + assert(!UnimplementedFeature::tbaa() && "NYI TBAA for bit fields"); + LValueBaseInfo FieldBaseInfo(BaseInfo.getAlignmentSource()); + return LValue::MakeBitfield(Addr, info, fieldType, FieldBaseInfo); +} + +LValue CIRGenFunction::buildLValueForField(LValue base, + const FieldDecl *field) { + LValueBaseInfo BaseInfo = base.getBaseInfo(); + + if (field->isBitField()) + return buildLValueForBitField(base, field); + + // Fields of may-alias structures are may-alais themselves. + // FIXME: this hould get propagated down through anonymous structs and unions. + QualType FieldType = field->getType(); + const RecordDecl *rec = field->getParent(); + AlignmentSource BaseAlignSource = BaseInfo.getAlignmentSource(); + LValueBaseInfo FieldBaseInfo(getFieldAlignmentSource(BaseAlignSource)); + if (UnimplementedFeature::tbaa() || rec->hasAttr() || + FieldType->isVectorType()) { + assert(!UnimplementedFeature::tbaa() && "NYI"); + } else if (rec->isUnion()) { + assert(!UnimplementedFeature::tbaa() && "NYI"); + } else { + // If no base type been assigned for the base access, then try to generate + // one for this base lvalue. + assert(!UnimplementedFeature::tbaa() && "NYI"); + } + + Address addr = base.getAddress(); + if (auto *ClassDef = dyn_cast(rec)) { + if (CGM.getCodeGenOpts().StrictVTablePointers && + ClassDef->isDynamicClass()) { + llvm_unreachable("NYI"); + } + } + + unsigned RecordCVR = base.getVRQualifiers(); + if (rec->isUnion()) { + // NOTE(cir): the element to be loaded/stored need to type-match the + // source/destination, so we emit a GetMemberOp here. + llvm::StringRef fieldName = field->getName(); + unsigned fieldIndex = field->getFieldIndex(); + if (CGM.LambdaFieldToName.count(field)) + fieldName = CGM.LambdaFieldToName[field]; + addr = buildAddrOfFieldStorage(*this, addr, field, fieldName, fieldIndex); + + if (CGM.getCodeGenOpts().StrictVTablePointers && + hasAnyVptr(FieldType, getContext())) + // Because unions can easily skip invariant.barriers, we need to add + // a barrier every time CXXRecord field with vptr is referenced. + assert(!UnimplementedFeature::createInvariantGroup()); + + if (IsInPreservedAIRegion || + (getDebugInfo() && rec->hasAttr())) { + assert(!UnimplementedFeature::generateDebugInfo()); + } + + if (FieldType->isReferenceType()) + llvm_unreachable("NYI"); + } else { + if (!IsInPreservedAIRegion && + (!getDebugInfo() || !rec->hasAttr())) { + llvm::StringRef fieldName = field->getName(); + auto& layout = CGM.getTypes().getCIRGenRecordLayout(field->getParent()); + unsigned fieldIndex = layout.getCIRFieldNo(field); + + if (CGM.LambdaFieldToName.count(field)) + fieldName = CGM.LambdaFieldToName[field]; + addr = buildAddrOfFieldStorage(*this, addr, field, fieldName, fieldIndex); + } else + // Remember the original struct field index + addr = buildPreserveStructAccess(*this, base, addr, field); + } + + // If this is a reference field, load the reference right now. + if (FieldType->isReferenceType()) { + assert(!UnimplementedFeature::tbaa()); + LValue RefLVal = makeAddrLValue(addr, FieldType, FieldBaseInfo); + if (RecordCVR & Qualifiers::Volatile) + RefLVal.getQuals().addVolatile(); + addr = buildLoadOfReference(RefLVal, getLoc(field->getSourceRange()), + &FieldBaseInfo); + + // Qualifiers on the struct don't apply to the referencee. + RecordCVR = 0; + FieldType = FieldType->getPointeeType(); + } + + // Make sure that the address is pointing to the right type. This is critical + // for both unions and structs. A union needs a bitcast, a struct element will + // need a bitcast if the CIR type laid out doesn't match the desired type. + // TODO(CIR): CodeGen requires a bitcast here for unions or for structs where + // the LLVM type doesn't match the desired type. No idea when the latter might + // occur, though. + + if (field->hasAttr()) + llvm_unreachable("NYI"); + + if (UnimplementedFeature::tbaa()) + // Next line should take a TBAA object + llvm_unreachable("NYI"); + LValue LV = makeAddrLValue(addr, FieldType, FieldBaseInfo); + LV.getQuals().addCVRQualifiers(RecordCVR); + + // __weak attribute on a field is ignored. + if (LV.getQuals().getObjCGCAttr() == Qualifiers::Weak) + llvm_unreachable("NYI"); + + return LV; +} + +LValue CIRGenFunction::buildLValueForFieldInitialization( + LValue Base, const clang::FieldDecl *Field, llvm::StringRef FieldName) { + QualType FieldType = Field->getType(); + + if (!FieldType->isReferenceType()) + return buildLValueForField(Base, Field); + + auto& layout = CGM.getTypes().getCIRGenRecordLayout(Field->getParent()); + unsigned FieldIndex = layout.getCIRFieldNo(Field); + + Address V = buildAddrOfFieldStorage(*this, Base.getAddress(), Field, + FieldName, FieldIndex); + + // Make sure that the address is pointing to the right type. + auto memTy = getTypes().convertTypeForMem(FieldType); + V = builder.createElementBitCast(getLoc(Field->getSourceRange()), V, memTy); + + // TODO: Generate TBAA information that describes this access as a structure + // member access and not just an access to an object of the field's type. This + // should be similar to what we do in EmitLValueForField(). + LValueBaseInfo BaseInfo = Base.getBaseInfo(); + AlignmentSource FieldAlignSource = BaseInfo.getAlignmentSource(); + LValueBaseInfo FieldBaseInfo(getFieldAlignmentSource(FieldAlignSource)); + assert(!UnimplementedFeature::tbaa() && "NYI"); + return makeAddrLValue(V, FieldType, FieldBaseInfo); +} + +// Detect the unusual situation where an inline version is shadowed by a +// non-inline version. In that case we should pick the external one +// everywhere. That's GCC behavior too. +static bool onlyHasInlineBuiltinDeclaration(const FunctionDecl *FD) { + for (const FunctionDecl *PD = FD; PD; PD = PD->getPreviousDecl()) + if (!PD->isInlineBuiltinDeclaration()) + return false; + return true; +} + +static CIRGenCallee buildDirectCallee(CIRGenModule &CGM, GlobalDecl GD) { + const auto *FD = cast(GD.getDecl()); + + if (auto builtinID = FD->getBuiltinID()) { + std::string NoBuiltinFD = ("no-builtin-" + FD->getName()).str(); + std::string NoBuiltins = "no-builtins"; + + auto *A = FD->getAttr(); + StringRef Ident = A ? A->getLabel() : FD->getName(); + std::string FDInlineName = (Ident + ".inline").str(); + + auto &CGF = *CGM.getCurrCIRGenFun(); + bool IsPredefinedLibFunction = + CGM.getASTContext().BuiltinInfo.isPredefinedLibFunction(builtinID); + bool HasAttributeNoBuiltin = false; + assert(!UnimplementedFeature::attributeNoBuiltin() && "NYI"); + // bool HasAttributeNoBuiltin = + // CGF.CurFn->getAttributes().hasFnAttr(NoBuiltinFD) || + // CGF.CurFn->getAttributes().hasFnAttr(NoBuiltins); + + // When directing calling an inline builtin, call it through it's mangled + // name to make it clear it's not the actual builtin. + auto Fn = cast(CGF.CurFn); + if (Fn.getName() != FDInlineName && onlyHasInlineBuiltinDeclaration(FD)) { + assert(0 && "NYI"); + } + + // Replaceable builtins provide their own implementation of a builtin. If we + // are in an inline builtin implementation, avoid trivial infinite + // recursion. Honor __attribute__((no_builtin("foo"))) or + // __attribute__((no_builtin)) on the current function unless foo is + // not a predefined library function which means we must generate the + // builtin no matter what. + else if (!IsPredefinedLibFunction || !HasAttributeNoBuiltin) + return CIRGenCallee::forBuiltin(builtinID, FD); + } + + auto CalleePtr = buildFunctionDeclPointer(CGM, GD); + + assert(!CGM.getLangOpts().CUDA && "NYI"); + + return CIRGenCallee::forDirect(CalleePtr, GD); +} + +// TODO: this can also be abstrated into common AST helpers +bool CIRGenFunction::hasBooleanRepresentation(QualType Ty) { + + if (Ty->isBooleanType()) + return true; + + if (const EnumType *ET = Ty->getAs()) + return ET->getDecl()->getIntegerType()->isBooleanType(); + + if (const AtomicType *AT = Ty->getAs()) + return hasBooleanRepresentation(AT->getValueType()); + + return false; +} + +CIRGenCallee CIRGenFunction::buildCallee(const clang::Expr *E) { + E = E->IgnoreParens(); + + // Look through function-to-pointer decay. + if (const auto *ICE = dyn_cast(E)) { + if (ICE->getCastKind() == CK_FunctionToPointerDecay || + ICE->getCastKind() == CK_BuiltinFnToFnPtr) { + return buildCallee(ICE->getSubExpr()); + } + // Resolve direct calls. + } else if (const auto *DRE = dyn_cast(E)) { + const auto *FD = dyn_cast(DRE->getDecl()); + assert(FD && + "DeclRef referring to FunctionDecl only thing supported so far"); + return buildDirectCallee(CGM, FD); + } + + assert(!dyn_cast(E) && "NYI"); + assert(!dyn_cast(E) && "NYI"); + assert(!dyn_cast(E) && "NYI"); + + // Otherwise, we have an indirect reference. + mlir::Value calleePtr; + QualType functionType; + if (auto ptrType = E->getType()->getAs()) { + calleePtr = buildScalarExpr(E); + functionType = ptrType->getPointeeType(); + } else { + functionType = E->getType(); + calleePtr = buildLValue(E).getPointer(); + } + assert(functionType->isFunctionType()); + + GlobalDecl GD; + if (const auto *VD = + dyn_cast_or_null(E->getReferencedDeclOfCallee())) + GD = GlobalDecl(VD); + + CIRGenCalleeInfo calleeInfo(functionType->getAs(), GD); + CIRGenCallee callee(calleeInfo, calleePtr.getDefiningOp()); + return callee; + + assert(false && "Nothing else supported yet!"); +} + +mlir::Value CIRGenFunction::buildToMemory(mlir::Value Value, QualType Ty) { + // Bool has a different representation in memory than in registers. + return Value; +} + +void CIRGenFunction::buildStoreOfScalar(mlir::Value value, LValue lvalue) { + // TODO: constant matrix type, volatile, no init, non temporal, TBAA + buildStoreOfScalar(value, lvalue.getAddress(), false, lvalue.getType(), + lvalue.getBaseInfo(), false, false); +} + +void CIRGenFunction::buildStoreOfScalar(mlir::Value Value, Address Addr, + bool Volatile, QualType Ty, + LValueBaseInfo BaseInfo, bool isInit, + bool isNontemporal) { + if (!CGM.getCodeGenOpts().PreserveVec3Type) { + if (Ty->isVectorType()) { + llvm_unreachable("NYI"); + } + } + + Value = buildToMemory(Value, Ty); + + if (Ty->isAtomicType()) { + llvm_unreachable("NYI"); + } + + // Update the alloca with more info on initialization. + assert(Addr.getPointer() && "expected pointer to exist"); + auto SrcAlloca = + dyn_cast_or_null(Addr.getPointer().getDefiningOp()); + if (currVarDecl && SrcAlloca) { + const VarDecl *VD = currVarDecl; + assert(VD && "VarDecl expected"); + if (VD->hasInit()) + SrcAlloca.setInitAttr(mlir::UnitAttr::get(builder.getContext())); + } + + assert(currSrcLoc && "must pass in source location"); + builder.create(*currSrcLoc, Value, Addr.getPointer()); + + if (isNontemporal) { + llvm_unreachable("NYI"); + } + + if (UnimplementedFeature::tbaa()) + llvm_unreachable("NYI"); +} + +void CIRGenFunction::buildStoreOfScalar(mlir::Value value, LValue lvalue, + bool isInit) { + if (lvalue.getType()->isConstantMatrixType()) { + llvm_unreachable("NYI"); + } + + buildStoreOfScalar(value, lvalue.getAddress(), lvalue.isVolatile(), + lvalue.getType(), lvalue.getBaseInfo(), isInit, + lvalue.isNontemporal()); +} + +/// 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::buildLoadOfLValue(LValue LV, SourceLocation Loc) { + assert(!LV.getType()->isFunctionType()); + assert(!(LV.getType()->isConstantMatrixType()) && "not implemented"); + + if (LV.isBitField()) + return buildLoadOfBitfieldLValue(LV, Loc); + + if (LV.isSimple()) + return RValue::get(buildLoadOfScalar(LV, Loc)); + llvm_unreachable("NYI"); +} + +RValue CIRGenFunction::buildLoadOfBitfieldLValue(LValue LV, + SourceLocation Loc) { + const CIRGenBitFieldInfo &Info = LV.getBitFieldInfo(); + + // Get the output type. + mlir::Type ResLTy = convertType(LV.getType()); + Address Ptr = LV.getBitFieldAddress(); + mlir::Value Val = builder.createLoad(getLoc(Loc), Ptr); + auto ValWidth = Val.getType().cast().getWidth(); + + bool UseVolatile = LV.isVolatileQualified() && + Info.VolatileStorageSize != 0 && isAAPCS(CGM.getTarget()); + const unsigned Offset = UseVolatile ? Info.VolatileOffset : Info.Offset; + const unsigned StorageSize = + UseVolatile ? Info.VolatileStorageSize : Info.StorageSize; + + if (Info.IsSigned) { + assert(static_cast(Offset + Info.Size) <= StorageSize); + + mlir::Type typ = builder.getSIntNTy(ValWidth); + Val = builder.createIntCast(Val, typ); + + unsigned HighBits = StorageSize - Offset - Info.Size; + if (HighBits) + Val = builder.createShiftLeft(Val, HighBits); + if (Offset + HighBits) + Val = builder.createShiftRight(Val, Offset + HighBits); + } else { + if (Offset) + Val = builder.createShiftRight(Val, Offset); + + if (static_cast(Offset) + Info.Size < StorageSize) + Val = builder.createAnd(Val, + llvm::APInt::getLowBitsSet(ValWidth, Info.Size)); + } + Val = builder.createIntCast(Val, ResLTy); + assert(!UnimplementedFeature::emitScalarRangeCheck() && "NYI"); + return RValue::get(Val); +} + +void CIRGenFunction::buildStoreThroughLValue(RValue Src, LValue Dst) { + assert(Dst.isSimple() && "only implemented simple"); + + // There's special magic for assigning into an ARC-qualified l-value. + if (Qualifiers::ObjCLifetime Lifetime = Dst.getQuals().getObjCLifetime()) { + llvm_unreachable("NYI"); + } + + if (Dst.isObjCWeak() && !Dst.isNonGC()) { + llvm_unreachable("NYI"); + } + + if (Dst.isObjCStrong() && !Dst.isNonGC()) { + llvm_unreachable("NYI"); + } + + assert(Src.isScalar() && "Can't emit an agg store with this method"); + buildStoreOfScalar(Src.getScalarVal(), Dst); +} + +void CIRGenFunction::buildStoreThroughBitfieldLValue(RValue Src, LValue Dst, + mlir::Value &Result) { + const CIRGenBitFieldInfo &Info = Dst.getBitFieldInfo(); + mlir::Type ResLTy = getTypes().convertTypeForMem(Dst.getType()); + Address Ptr = Dst.getBitFieldAddress(); + + // Get the source value, truncated to the width of the bit-field. + mlir::Value SrcVal = Src.getScalarVal(); + + // Cast the source to the storage type and shift it into place. + SrcVal = builder.createIntCast(SrcVal, Ptr.getElementType()); + auto SrcWidth = SrcVal.getType().cast().getWidth(); + mlir::Value MaskedVal = SrcVal; + + const bool UseVolatile = + CGM.getCodeGenOpts().AAPCSBitfieldWidth && Dst.isVolatileQualified() && + Info.VolatileStorageSize != 0 && isAAPCS(CGM.getTarget()); + const unsigned StorageSize = + UseVolatile ? Info.VolatileStorageSize : Info.StorageSize; + const unsigned Offset = UseVolatile ? Info.VolatileOffset : Info.Offset; + // See if there are other bits in the bitfield's storage we'll need to load + // and mask together with source before storing. + if (StorageSize != Info.Size) { + assert(StorageSize > Info.Size && "Invalid bitfield size."); + + mlir::Value Val = buildLoadOfScalar(Dst, Dst.getPointer().getLoc()); + + // Mask the source value as needed. + if (!hasBooleanRepresentation(Dst.getType())) + SrcVal = builder.createAnd( + SrcVal, llvm::APInt::getLowBitsSet(SrcWidth, Info.Size)); + + MaskedVal = SrcVal; + if (Offset) + SrcVal = builder.createShiftLeft(SrcVal, Offset); + + // Mask out the original value. + Val = builder.createAnd( + Val, ~llvm::APInt::getBitsSet(SrcWidth, Offset, Offset + Info.Size)); + + // Or together the unchanged values and the source value. + SrcVal = builder.createOr(Val, SrcVal); + + } else { + // According to the AACPS: + // When a volatile bit-field is written, and its container does not overlap + // with any non-bit-field member, its container must be read exactly once + // and written exactly once using the access width appropriate to the type + // of the container. The two accesses are not atomic. + if (Dst.isVolatileQualified() && isAAPCS(CGM.getTarget()) && + CGM.getCodeGenOpts().ForceAAPCSBitfieldLoad) + llvm_unreachable("volatile bit-field is not implemented for the AACPS"); + } + + // Write the new value back out. + // TODO: constant matrix type, volatile, no init, non temporal, TBAA + buildStoreOfScalar(SrcVal, Ptr, Dst.isVolatileQualified(), Dst.getType(), + Dst.getBaseInfo(), false, false); + + // Return the new value of the bit-field. + mlir::Value ResultVal = MaskedVal; + ResultVal = builder.createIntCast(ResultVal, ResLTy); + + // Sign extend the value if needed. + if (Info.IsSigned) { + assert(Info.Size <= StorageSize); + unsigned HighBits = StorageSize - Info.Size; + + if (HighBits) { + ResultVal = builder.createShiftLeft(ResultVal, HighBits); + ResultVal = builder.createShiftRight(ResultVal, HighBits); + } + } + + Result = buildFromMemory(ResultVal, Dst.getType()); +} + +static LValue buildGlobalVarDeclLValue(CIRGenFunction &CGF, const Expr *E, + const VarDecl *VD) { + QualType T = E->getType(); + + // If it's thread_local, emit a call to its wrapper function instead. + if (VD->getTLSKind() == VarDecl::TLS_Dynamic && + CGF.CGM.getCXXABI().usesThreadWrapperFunction(VD)) + assert(0 && "not implemented"); + + // Check if the variable is marked as declare target with link clause in + // device codegen. + if (CGF.getLangOpts().OpenMPIsTargetDevice) { + assert(0 && "not implemented"); + } + + auto V = CGF.CGM.getAddrOfGlobalVar(VD); + auto RealVarTy = CGF.getTypes().convertTypeForMem(VD->getType()); + // TODO(cir): do we need this for CIR? + // V = EmitBitCastOfLValueToProperType(CGF, V, RealVarTy); + CharUnits Alignment = CGF.getContext().getDeclAlign(VD); + Address Addr(V, RealVarTy, Alignment); + // Emit reference to the private copy of the variable if it is an OpenMP + // threadprivate variable. + if (CGF.getLangOpts().OpenMP && !CGF.getLangOpts().OpenMPSimd && + VD->hasAttr()) { + assert(0 && "NYI"); + } + LValue LV; + if (VD->getType()->isReferenceType()) + assert(0 && "NYI"); + else + LV = CGF.makeAddrLValue(Addr, T, AlignmentSource::Decl); + assert(!UnimplementedFeature::setObjCGCLValueClass() && "NYI"); + return LV; +} + +static LValue buildCapturedFieldLValue(CIRGenFunction &CGF, const FieldDecl *FD, + mlir::Value ThisValue) { + QualType TagType = CGF.getContext().getTagDeclType(FD->getParent()); + LValue LV = CGF.MakeNaturalAlignAddrLValue(ThisValue, TagType); + return CGF.buildLValueForField(LV, FD); +} + +static LValue buildFunctionDeclLValue(CIRGenFunction &CGF, const Expr *E, + GlobalDecl GD) { + const FunctionDecl *FD = cast(GD.getDecl()); + auto funcOp = buildFunctionDeclPointer(CGF.CGM, GD); + auto loc = CGF.getLoc(E->getSourceRange()); + CharUnits align = CGF.getContext().getDeclAlign(FD); + + auto fnTy = funcOp.getFunctionType(); + auto ptrTy = mlir::cir::PointerType::get(CGF.getBuilder().getContext(), fnTy); + auto addr = CGF.getBuilder().create( + loc, ptrTy, funcOp.getSymName()); + return CGF.makeAddrLValue(Address(addr, fnTy, align), E->getType(), + AlignmentSource::Decl); +} + +LValue CIRGenFunction::buildDeclRefLValue(const DeclRefExpr *E) { + const NamedDecl *ND = E->getDecl(); + QualType T = E->getType(); + + assert(E->isNonOdrUse() != NOUR_Unevaluated && + "should not emit an unevaluated operand"); + + if (const auto *VD = dyn_cast(ND)) { + // Global Named registers access via intrinsics only + if (VD->getStorageClass() == SC_Register && VD->hasAttr() && + !VD->isLocalVarDecl()) + llvm_unreachable("NYI"); + + assert(E->isNonOdrUse() != NOUR_Constant && "not implemented"); + + // Check for captured variables. + if (E->refersToEnclosingVariableOrCapture()) { + VD = VD->getCanonicalDecl(); + if (auto *FD = LambdaCaptureFields.lookup(VD)) + return buildCapturedFieldLValue(*this, FD, CXXABIThisValue); + assert(!UnimplementedFeature::CGCapturedStmtInfo() && "NYI"); + llvm_unreachable("NYI"); + // LLVM codegen: + // Address addr = GetAddrOfBlockDecl(VD); + // return MakeAddrLValue(addr, T, AlignmentSource::Decl); + } + } + + // FIXME(CIR): We should be able to assert this for FunctionDecls as well! + // FIXME(CIR): We should be able to assert this for all DeclRefExprs, not just + // those with a valid source location. + assert((ND->isUsed(false) || !isa(ND) || E->isNonOdrUse() || + !E->getLocation().isValid()) && + "Should not use decl without marking it used!"); + + if (ND->hasAttr()) { + llvm_unreachable("NYI"); + } + + if (const auto *VD = dyn_cast(ND)) { + // Check if this is a global variable + if (VD->hasLinkage() || VD->isStaticDataMember()) + return buildGlobalVarDeclLValue(*this, E, VD); + + 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; + } + // Otherwise, it might be static local we haven't emitted yet for some + // reason; most likely, because it's in an outer function. + else if (VD->isStaticLocal()) { + mlir::cir::GlobalOp var = CGM.getOrCreateStaticVarDecl( + *VD, CGM.getCIRLinkageVarDefinition(VD, /*IsConstant=*/false)); + addr = Address(builder.createGetGlobal(var), convertType(VD->getType()), + getContext().getDeclAlign(VD)); + } else { + llvm_unreachable("DeclRefExpr for decl not entered in LocalDeclMap?"); + } + + // Handle threadlocal function locals. + if (VD->getTLSKind() != VarDecl::TLS_None) + llvm_unreachable("thread-local storage is NYI"); + + // Check for OpenMP threadprivate variables. + if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd && + VD->hasAttr()) { + llvm_unreachable("NYI"); + } + + // Drill into block byref variables. + bool isBlockByref = VD->isEscapingByref(); + if (isBlockByref) { + llvm_unreachable("NYI"); + } + + // Drill into reference types. + LValue LV = + VD->getType()->isReferenceType() + ? buildLoadOfReferenceLValue(addr, getLoc(E->getSourceRange()), + VD->getType(), AlignmentSource::Decl) + : makeAddrLValue(addr, T, AlignmentSource::Decl); + + // Statics are defined as globals, so they are not include in the function's + // symbol table. + assert((VD->isStaticLocal() || symbolTable.count(VD)) && + "non-static locals should be already mapped"); + + bool isLocalStorage = VD->hasLocalStorage(); + + bool NonGCable = + isLocalStorage && !VD->getType()->isReferenceType() && !isBlockByref; + + if (NonGCable && UnimplementedFeature::setNonGC()) { + llvm_unreachable("garbage collection is NYI"); + } + + bool isImpreciseLifetime = + (isLocalStorage && !VD->hasAttr()); + if (isImpreciseLifetime && UnimplementedFeature::ARC()) + llvm_unreachable("imprecise lifetime is NYI"); + assert(!UnimplementedFeature::setObjCGCLValueClass()); + + // Statics are defined as globals, so they are not include in the function's + // symbol table. + assert((VD->isStaticLocal() || symbolTable.lookup(VD)) && + "Name lookup must succeed for non-static local variables"); + + return LV; + } + + if (const auto *FD = dyn_cast(ND)) { + LValue LV = buildFunctionDeclLValue(*this, E, FD); + + // Emit debuginfo for the function declaration if the target wants to. + if (getContext().getTargetInfo().allowDebugInfoForExternalRef()) + assert(!UnimplementedFeature::generateDebugInfo()); + + return LV; + } + + // FIXME: While we're emitting a binding from an enclosing scope, all other + // DeclRefExprs we see should be implicitly treated as if they also refer to + // an enclosing scope. + if (const auto *BD = dyn_cast(ND)) { + llvm_unreachable("NYI"); + } + + // We can form DeclRefExprs naming GUID declarations when reconstituting + // non-type template parameters into expressions. + if (const auto *GD = dyn_cast(ND)) + llvm_unreachable("NYI"); + + if (const auto *TPO = dyn_cast(ND)) + llvm_unreachable("NYI"); + + llvm_unreachable("Unhandled DeclRefExpr"); +} + +LValue CIRGenFunction::buildBinaryOperatorLValue(const BinaryOperator *E) { + // Comma expressions just emit their LHS then their RHS as an l-value. + if (E->getOpcode() == BO_Comma) { + assert(0 && "not implemented"); + } + + if (E->getOpcode() == BO_PtrMemD || E->getOpcode() == BO_PtrMemI) + assert(0 && "not implemented"); + + assert(E->getOpcode() == BO_Assign && "unexpected binary l-value"); + + // Note that in all of these cases, __block variables need the RHS + // evaluated first just in case the variable gets moved by the RHS. + + switch (CIRGenFunction::getEvaluationKind(E->getType())) { + case TEK_Scalar: { + assert(E->getLHS()->getType().getObjCLifetime() == + clang::Qualifiers::ObjCLifetime::OCL_None && + "not implemented"); + + RValue RV = buildAnyExpr(E->getRHS()); + LValue LV = buildLValue(E->getLHS()); + + SourceLocRAIIObject Loc{*this, getLoc(E->getSourceRange())}; + if (LV.isBitField()) { + mlir::Value result; + buildStoreThroughBitfieldLValue(RV, LV, result); + } else { + buildStoreThroughLValue(RV, LV); + } + + assert(!getContext().getLangOpts().OpenMP && + "last priv cond not implemented"); + return LV; + } + + case TEK_Complex: + assert(0 && "not implemented"); + case TEK_Aggregate: + assert(0 && "not implemented"); + } + llvm_unreachable("bad evaluation kind"); +} + +/// Given an expression of pointer type, try to +/// derive a more accurate bound on the alignment of the pointer. +Address CIRGenFunction::buildPointerWithAlignment( + const Expr *E, LValueBaseInfo *BaseInfo, KnownNonNull_t IsKnownNonNull) { + Address Addr = + ::buildPointerWithAlignment(E, BaseInfo, IsKnownNonNull, *this); + if (IsKnownNonNull && !Addr.isKnownNonNull()) + Addr.setKnownNonNull(); + return Addr; +} + +/// Perform the usual unary conversions on the specified +/// expression and compare the result against zero, returning an Int1Ty value. +mlir::Value CIRGenFunction::evaluateExprAsBool(const Expr *E) { + // TODO(cir): PGO + if (const MemberPointerType *MPT = E->getType()->getAs()) { + assert(0 && "not implemented"); + } + + QualType BoolTy = getContext().BoolTy; + SourceLocation Loc = E->getExprLoc(); + // TODO(cir): CGFPOptionsRAII for FP stuff. + if (!E->getType()->isAnyComplexType()) + return buildScalarConversion(buildScalarExpr(E), E->getType(), BoolTy, Loc); + + llvm_unreachable("complex to scalar not implemented"); +} + +LValue CIRGenFunction::buildUnaryOpLValue(const UnaryOperator *E) { + // __extension__ doesn't affect lvalue-ness. + assert(E->getOpcode() != UO_Extension && "not implemented"); + + switch (E->getOpcode()) { + default: + llvm_unreachable("Unknown unary operator lvalue!"); + case UO_Deref: { + QualType T = E->getSubExpr()->getType()->getPointeeType(); + assert(!T.isNull() && "CodeGenFunction::EmitUnaryOpLValue: Illegal type"); + + LValueBaseInfo BaseInfo; + // TODO: add TBAAInfo + Address Addr = buildPointerWithAlignment(E->getSubExpr(), &BaseInfo); + + // Tag 'load' with deref attribute. + if (auto loadOp = + dyn_cast<::mlir::cir::LoadOp>(Addr.getPointer().getDefiningOp())) { + loadOp.setIsDerefAttr(mlir::UnitAttr::get(builder.getContext())); + } + + LValue LV = LValue::makeAddr(Addr, T, BaseInfo); + // TODO: set addr space + // TODO: ObjC/GC/__weak write barrier stuff. + return LV; + } + case UO_Real: + case UO_Imag: { + assert(0 && "not implemented"); + } + case UO_PreInc: + case UO_PreDec: { + bool isInc = E->isIncrementOp(); + bool isPre = E->isPrefix(); + LValue LV = buildLValue(E->getSubExpr()); + + if (E->getType()->isAnyComplexType()) { + assert(0 && "not implemented"); + } else { + buildScalarPrePostIncDec(E, LV, isInc, isPre); + } + + return LV; + } + } +} + +/// Emit code to compute the specified expression which +/// can have any type. The result is returned as an RValue struct. +RValue CIRGenFunction::buildAnyExpr(const Expr *E, AggValueSlot aggSlot, + bool ignoreResult) { + switch (CIRGenFunction::getEvaluationKind(E->getType())) { + case TEK_Scalar: + return RValue::get(buildScalarExpr(E)); + case TEK_Complex: + assert(0 && "not implemented"); + case TEK_Aggregate: { + if (!ignoreResult && aggSlot.isIgnored()) + aggSlot = CreateAggTemp(E->getType(), getLoc(E->getSourceRange()), + getCounterAggTmpAsString()); + buildAggExpr(E, aggSlot); + return aggSlot.asRValue(); + } + } + llvm_unreachable("bad evaluation kind"); +} + +RValue CIRGenFunction::buildCallExpr(const clang::CallExpr *E, + ReturnValueSlot ReturnValue) { + assert(!E->getCallee()->getType()->isBlockPointerType() && "ObjC Blocks NYI"); + + if (const auto *CE = dyn_cast(E)) + return buildCXXMemberCallExpr(CE, ReturnValue); + + assert(!dyn_cast(E) && "CUDA NYI"); + if (const auto *CE = dyn_cast(E)) + if (const CXXMethodDecl *MD = + dyn_cast_or_null(CE->getCalleeDecl())) + return buildCXXOperatorMemberCallExpr(CE, MD, ReturnValue); + + CIRGenCallee callee = buildCallee(E->getCallee()); + + if (callee.isBuiltin()) + return buildBuiltinExpr(callee.getBuiltinDecl(), callee.getBuiltinID(), E, + ReturnValue); + + assert(!callee.isPsuedoDestructor() && "NYI"); + + return buildCall(E->getCallee()->getType(), callee, E, ReturnValue); +} + +RValue CIRGenFunction::buildCall(clang::QualType CalleeType, + const CIRGenCallee &OrigCallee, + const clang::CallExpr *E, + ReturnValueSlot ReturnValue, + mlir::Value Chain) { + // Get the actual function type. The callee type will always be a pointer to + // function type or a block pointer type. + assert(CalleeType->isFunctionPointerType() && + "Call must have function pointer type!"); + + auto *TargetDecl = OrigCallee.getAbstractInfo().getCalleeDecl().getDecl(); + (void)TargetDecl; + + CalleeType = getContext().getCanonicalType(CalleeType); + + auto PointeeType = cast(CalleeType)->getPointeeType(); + + CIRGenCallee Callee = OrigCallee; + + if (getLangOpts().CPlusPlus) + assert(!SanOpts.has(SanitizerKind::Function) && "Sanitizers NYI"); + + const auto *FnType = cast(PointeeType); + + assert(!SanOpts.has(SanitizerKind::CFIICall) && "Sanitizers NYI"); + + CallArgList Args; + + assert(!Chain && "FIX THIS"); + + // C++17 requires that we evaluate arguments to a call using assignment syntax + // right-to-left, and that we evaluate arguments to certain other operators + // left-to-right. Note that we allow this to override the order dictated by + // the calling convention on the MS ABI, which means that parameter + // destruction order is not necessarily reverse construction order. + // FIXME: Revisit this based on C++ committee response to unimplementability. + EvaluationOrder Order = EvaluationOrder::Default; + if (auto *OCE = dyn_cast(E)) { + if (OCE->isAssignmentOp()) + Order = EvaluationOrder::ForceRightToLeft; + else { + switch (OCE->getOperator()) { + case OO_LessLess: + case OO_GreaterGreater: + case OO_AmpAmp: + case OO_PipePipe: + case OO_Comma: + case OO_ArrowStar: + Order = EvaluationOrder::ForceLeftToRight; + break; + default: + break; + } + } + } + + buildCallArgs(Args, dyn_cast(FnType), E->arguments(), + E->getDirectCallee(), /*ParamsToSkip*/ 0, Order); + + const CIRGenFunctionInfo &FnInfo = CGM.getTypes().arrangeFreeFunctionCall( + Args, FnType, /*ChainCall=*/Chain.getAsOpaquePointer()); + + // C99 6.5.2.2p6: + // If the expression that denotes the called function has a type that does + // not include a prototype, [the default argument promotions are performed]. + // If the number of arguments does not equal the number of parameters, the + // behavior is undefined. If the function is defined with at type that + // includes a prototype, and either the prototype ends with an ellipsis (, + // ...) or the types of the arguments after promotion are not compatible + // with the types of the parameters, the behavior is undefined. If the + // function is defined with a type that does not include a prototype, and + // the types of the arguments after promotion are not compatible with those + // of the parameters after promotion, the behavior is undefined [except in + // some trivial cases]. + // That is, in the general case, we should assume that a call through an + // unprototyped function type works like a *non-variadic* call. The way we + // make this work is to cast to the exxact type fo the promoted arguments. + // + // Chain calls use the same code path to add the inviisble chain parameter to + // the function type. + if (isa(FnType) || Chain) { + assert(!UnimplementedFeature::chainCalls()); + assert(!UnimplementedFeature::addressSpace()); + + // Set no-proto function as callee. + auto Fn = llvm::dyn_cast(Callee.getFunctionPointer()); + Callee.setFunctionPointer(Fn); + } + + assert(!CGM.getLangOpts().HIP && "HIP NYI"); + + assert(!MustTailCall && "Must tail NYI"); + mlir::cir::CallOp callOP = nullptr; + RValue Call = buildCall(FnInfo, Callee, ReturnValue, Args, &callOP, + E == MustTailCall, getLoc(E->getExprLoc())); + + assert(!getDebugInfo() && "Debug Info NYI"); + + return Call; +} + +/// Emit code to compute the specified expression, ignoring the result. +void CIRGenFunction::buildIgnoredExpr(const Expr *E) { + if (E->isPRValue()) + return (void)buildAnyExpr(E); + + // Just emit it as an l-value and drop the result. + buildLValue(E); +} + +static mlir::Value maybeBuildArrayDecay(mlir::OpBuilder &builder, + mlir::Location loc, + mlir::Value arrayPtr, + mlir::Type eltTy) { + auto arrayPtrTy = arrayPtr.getType().dyn_cast<::mlir::cir::PointerType>(); + assert(arrayPtrTy && "expected pointer type"); + auto arrayTy = arrayPtrTy.getPointee().dyn_cast<::mlir::cir::ArrayType>(); + + if (arrayTy) { + mlir::cir::PointerType flatPtrTy = + mlir::cir::PointerType::get(builder.getContext(), arrayTy.getEltType()); + return builder.create( + loc, flatPtrTy, mlir::cir::CastKind::array_to_ptrdecay, arrayPtr); + } + + assert(arrayPtrTy.getPointee() == eltTy && + "flat pointee type must match original array element type"); + return arrayPtr; +} + +Address CIRGenFunction::buildArrayToPointerDecay(const Expr *E, + LValueBaseInfo *BaseInfo) { + assert(E->getType()->isArrayType() && + "Array to pointer decay must have array source type!"); + + // Expressions of array type can't be bitfields or vector elements. + LValue LV = buildLValue(E); + Address Addr = LV.getAddress(); + + // If the array type was an incomplete type, we need to make sure + // the decay ends up being the right type. + auto lvalueAddrTy = + Addr.getPointer().getType().dyn_cast(); + assert(lvalueAddrTy && "expected pointer"); + + auto pointeeTy = lvalueAddrTy.getPointee().dyn_cast(); + assert(pointeeTy && "expected array"); + + mlir::Type arrayTy = convertType(E->getType()); + assert(arrayTy.isa() && "expected array"); + assert(pointeeTy == arrayTy); + + // TODO(cir): in LLVM codegen VLA pointers are always decayed, so we don't + // need to do anything here. Revisit this for VAT when its supported in CIR. + assert(!E->getType()->isVariableArrayType() && "what now?"); + + // The result of this decay conversion points to an array element within the + // base lvalue. However, since TBAA currently does not support representing + // accesses to elements of member arrays, we conservatively represent accesses + // to the pointee object as if it had no any base lvalue specified. + // TODO: Support TBAA for member arrays. + QualType EltType = E->getType()->castAsArrayTypeUnsafe()->getElementType(); + if (BaseInfo) + *BaseInfo = LV.getBaseInfo(); + assert(!UnimplementedFeature::tbaa() && "NYI"); + + mlir::Value ptr = maybeBuildArrayDecay( + CGM.getBuilder(), CGM.getLoc(E->getSourceRange()), Addr.getPointer(), + getTypes().convertTypeForMem(EltType)); + return Address(ptr, Addr.getAlignment()); +} + +/// If the specified expr is a simple decay from an array to pointer, +/// return the array subexpression. +/// FIXME: this could be abstracted into a commeon AST helper. +static const Expr *isSimpleArrayDecayOperand(const Expr *E) { + // If this isn't just an array->pointer decay, bail out. + const auto *CE = dyn_cast(E); + if (!CE || CE->getCastKind() != CK_ArrayToPointerDecay) + return nullptr; + + // If this is a decay from variable width array, bail out. + const Expr *SubExpr = CE->getSubExpr(); + if (SubExpr->getType()->isVariableArrayType()) + return nullptr; + + return SubExpr; +} + +/// Given an array base, check whether its member access belongs to a record +/// with preserve_access_index attribute or not. +/// TODO(cir): don't need to be specific to LLVM's codegen, refactor into common +/// AST helpers. +static bool isPreserveAIArrayBase(CIRGenFunction &CGF, const Expr *ArrayBase) { + if (!ArrayBase || !CGF.getDebugInfo()) + return false; + + // Only support base as either a MemberExpr or DeclRefExpr. + // DeclRefExpr to cover cases like: + // struct s { int a; int b[10]; }; + // struct s *p; + // p[1].a + // p[1] will generate a DeclRefExpr and p[1].a is a MemberExpr. + // p->b[5] is a MemberExpr example. + const Expr *E = ArrayBase->IgnoreImpCasts(); + if (const auto *ME = dyn_cast(E)) + return ME->getMemberDecl()->hasAttr(); + + if (const auto *DRE = dyn_cast(E)) { + const auto *VarDef = dyn_cast(DRE->getDecl()); + if (!VarDef) + return false; + + const auto *PtrT = VarDef->getType()->getAs(); + if (!PtrT) + return false; + + const auto *PointeeT = + PtrT->getPointeeType()->getUnqualifiedDesugaredType(); + if (const auto *RecT = dyn_cast(PointeeT)) + return RecT->getDecl()->hasAttr(); + return false; + } + + return false; +} + +static mlir::IntegerAttr getConstantIndexOrNull(mlir::Value idx) { + // TODO(cir): should we consider using MLIRs IndexType instead of IntegerAttr? + if (auto constantOp = dyn_cast(idx.getDefiningOp())) + return constantOp.getValue().dyn_cast(); + return {}; +} + +static CharUnits getArrayElementAlign(CharUnits arrayAlign, mlir::Value idx, + CharUnits eltSize) { + // If we have a constant index, we can use the exact offset of the + // element we're accessing. + auto constantIdx = getConstantIndexOrNull(idx); + if (constantIdx) { + CharUnits offset = constantIdx.getValue().getZExtValue() * eltSize; + return arrayAlign.alignmentAtOffset(offset); + // Otherwise, use the worst-case alignment for any element. + } else { + return arrayAlign.alignmentOfArrayElement(eltSize); + } +} + +static mlir::Value buildArrayAccessOp(mlir::OpBuilder &builder, + mlir::Location arrayLocBegin, + mlir::Location arrayLocEnd, + mlir::Value arrayPtr, mlir::Type eltTy, + mlir::Value idx, bool shouldDecay) { + mlir::Value basePtr = arrayPtr; + if (shouldDecay) + basePtr = maybeBuildArrayDecay(builder, arrayLocBegin, arrayPtr, eltTy); + mlir::Type flatPtrTy = basePtr.getType(); + + return builder.create(arrayLocEnd, flatPtrTy, basePtr, + idx); +} + +static mlir::Value +buildArraySubscriptPtr(CIRGenFunction &CGF, mlir::Location beginLoc, + mlir::Location endLoc, mlir::Value ptr, mlir::Type eltTy, + ArrayRef indices, bool inbounds, + bool signedIndices, bool shouldDecay, + const llvm::Twine &name = "arrayidx") { + assert(indices.size() == 1 && "cannot handle multiple indices yet"); + auto idx = indices.back(); + auto &CGM = CGF.getCIRGenModule(); + // TODO(cir): LLVM codegen emits in bound gep check here, is there anything + // that would enhance tracking this later in CIR? + if (inbounds) + assert(!UnimplementedFeature::emitCheckedInBoundsGEP() && "NYI"); + return buildArrayAccessOp(CGM.getBuilder(), beginLoc, endLoc, ptr, eltTy, idx, + shouldDecay); +} + +static Address buildArraySubscriptPtr( + CIRGenFunction &CGF, mlir::Location beginLoc, mlir::Location endLoc, + Address addr, ArrayRef indices, QualType eltType, + bool inbounds, bool signedIndices, mlir::Location loc, bool shouldDecay, + QualType *arrayType = nullptr, const Expr *Base = nullptr, + const llvm::Twine &name = "arrayidx") { + // Determine the element size of the statically-sized base. This is + // the thing that the indices are expressed in terms of. + if (auto vla = CGF.getContext().getAsVariableArrayType(eltType)) { + assert(0 && "not implemented"); + } + + // We can use that to compute the best alignment of the element. + CharUnits eltSize = CGF.getContext().getTypeSizeInChars(eltType); + CharUnits eltAlign = + getArrayElementAlign(addr.getAlignment(), indices.back(), eltSize); + + mlir::Value eltPtr; + auto LastIndex = getConstantIndexOrNull(indices.back()); + if (!LastIndex || + (!CGF.IsInPreservedAIRegion && !isPreserveAIArrayBase(CGF, Base))) { + eltPtr = buildArraySubscriptPtr(CGF, beginLoc, endLoc, addr.getPointer(), + addr.getElementType(), indices, inbounds, + signedIndices, shouldDecay, name); + } else { + // assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + // assert(indices.size() == 1 && "cannot handle multiple indices yet"); + // auto idx = indices.back(); + // auto &CGM = CGF.getCIRGenModule(); + // eltPtr = buildArrayAccessOp(CGM.getBuilder(), beginLoc, endLoc, + // addr.getPointer(), addr.getElementType(), + // idx); + assert(0 && "NYI"); + } + + return Address(eltPtr, CGF.getTypes().convertTypeForMem(eltType), eltAlign); +} + +LValue CIRGenFunction::buildArraySubscriptExpr(const ArraySubscriptExpr *E, + bool Accessed) { + // The index must always be an integer, which is not an aggregate. Emit it + // in lexical order (this complexity is, sadly, required by C++17). + mlir::Value IdxPre = + (E->getLHS() == E->getIdx()) ? buildScalarExpr(E->getIdx()) : nullptr; + bool SignedIndices = false; + auto EmitIdxAfterBase = [&, IdxPre](bool Promote) -> mlir::Value { + mlir::Value Idx = IdxPre; + if (E->getLHS() != E->getIdx()) { + assert(E->getRHS() == E->getIdx() && "index was neither LHS nor RHS"); + Idx = buildScalarExpr(E->getIdx()); + } + + QualType IdxTy = E->getIdx()->getType(); + bool IdxSigned = IdxTy->isSignedIntegerOrEnumerationType(); + SignedIndices |= IdxSigned; + + if (SanOpts.has(SanitizerKind::ArrayBounds)) + llvm_unreachable("array bounds sanitizer is NYI"); + + // Extend or truncate the index type to 32 or 64-bits. + auto ptrTy = Idx.getType().dyn_cast(); + if (Promote && ptrTy && ptrTy.getPointee().isa()) + llvm_unreachable("index type cast is NYI"); + + return Idx; + }; + IdxPre = nullptr; + + // If the base is a vector type, then we are forming a vector element + // with this subscript. + if (E->getBase()->getType()->isVectorType() && + !isa(E->getBase())) { + llvm_unreachable("vector subscript is NYI"); + } + + // All the other cases basically behave like simple offsetting. + + // Handle the extvector case we ignored above. + if (isa(E->getBase())) { + llvm_unreachable("extvector subscript is NYI"); + } + + assert(!UnimplementedFeature::tbaa() && "TBAA is NYI"); + LValueBaseInfo EltBaseInfo; + Address Addr = Address::invalid(); + if (const VariableArrayType *vla = + getContext().getAsVariableArrayType(E->getType())) { + llvm_unreachable("variable array subscript is NYI"); + } else if (const ObjCObjectType *OIT = + E->getType()->getAs()) { + llvm_unreachable("ObjC object type subscript is NYI"); + } else if (const Expr *Array = isSimpleArrayDecayOperand(E->getBase())) { + // If this is A[i] where A is an array, the frontend will have decayed + // the base to be a ArrayToPointerDecay implicit cast. While correct, it is + // inefficient at -O0 to emit a "gep A, 0, 0" when codegen'ing it, then + // a "gep x, i" here. Emit one "gep A, 0, i". + assert(Array->getType()->isArrayType() && + "Array to pointer decay must have array source type!"); + LValue ArrayLV; + // For simple multidimensional array indexing, set the 'accessed' flag + // for better bounds-checking of the base expression. + if (const auto *ASE = dyn_cast(Array)) + ArrayLV = buildArraySubscriptExpr(ASE, /*Accessed=*/true); + else + ArrayLV = buildLValue(Array); + auto Idx = EmitIdxAfterBase(/*Promote=*/true); + + // Propagate the alignment from the array itself to the result. + QualType arrayType = Array->getType(); + Addr = buildArraySubscriptPtr( + *this, CGM.getLoc(Array->getBeginLoc()), CGM.getLoc(Array->getEndLoc()), + ArrayLV.getAddress(), {Idx}, E->getType(), + !getLangOpts().isSignedOverflowDefined(), SignedIndices, + CGM.getLoc(E->getExprLoc()), /*shouldDecay=*/true, &arrayType, + E->getBase()); + EltBaseInfo = ArrayLV.getBaseInfo(); + // TODO(cir): EltTBAAInfo + assert(!UnimplementedFeature::tbaa() && "TBAA is NYI"); + } else { + // The base must be a pointer; emit it with an estimate of its alignment. + // TODO(cir): EltTBAAInfo + assert(!UnimplementedFeature::tbaa() && "TBAA is NYI"); + Addr = buildPointerWithAlignment(E->getBase(), &EltBaseInfo); + auto Idx = EmitIdxAfterBase(/*Promote*/ true); + QualType ptrType = E->getBase()->getType(); + Addr = buildArraySubscriptPtr( + *this, CGM.getLoc(E->getBeginLoc()), CGM.getLoc(E->getEndLoc()), Addr, + Idx, E->getType(), !getLangOpts().isSignedOverflowDefined(), + SignedIndices, CGM.getLoc(E->getExprLoc()), /*shouldDecay=*/false, + &ptrType, E->getBase()); + } + + LValue LV = LValue::makeAddr(Addr, E->getType(), EltBaseInfo); + + if (getLangOpts().ObjC && getLangOpts().getGC() != LangOptions::NonGC) { + llvm_unreachable("ObjC is NYI"); + } + + return LV; +} + +LValue CIRGenFunction::buildStringLiteralLValue(const StringLiteral *E) { + auto sym = CGM.getAddrOfConstantStringFromLiteral(E).getSymbol(); + + auto cstGlobal = mlir::SymbolTable::lookupSymbolIn(CGM.getModule(), sym); + assert(cstGlobal && "Expected global"); + + auto g = dyn_cast(cstGlobal); + assert(g && "unaware of other symbol providers"); + + auto ptrTy = mlir::cir::PointerType::get(CGM.getBuilder().getContext(), + g.getSymType()); + assert(g.getAlignment() && "expected alignment for string literal"); + auto align = *g.getAlignment(); + auto addr = builder.create( + getLoc(E->getSourceRange()), ptrTy, g.getSymName()); + return makeAddrLValue( + Address(addr, g.getSymType(), CharUnits::fromQuantity(align)), + E->getType(), AlignmentSource::Decl); +} + +/// Casts are never lvalues unless that cast is to a reference type. If the cast +/// is to a reference, we can have the usual lvalue result, otherwise if a cast +/// is needed by the code generator in an lvalue context, then it must mean that +/// we need the address of an aggregate in order to access one of its members. +/// This can happen for all the reasons that casts are permitted with aggregate +/// result, including noop aggregate casts, and cast from scalar to union. +LValue CIRGenFunction::buildCastLValue(const CastExpr *E) { + switch (E->getCastKind()) { + case CK_ToVoid: + case CK_BitCast: + case CK_LValueToRValueBitCast: + case CK_ArrayToPointerDecay: + case CK_FunctionToPointerDecay: + case CK_NullToMemberPointer: + case CK_NullToPointer: + case CK_IntegralToPointer: + case CK_PointerToIntegral: + case CK_PointerToBoolean: + case CK_VectorSplat: + case CK_IntegralCast: + case CK_BooleanToSignedIntegral: + case CK_IntegralToBoolean: + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + case CK_FloatingToBoolean: + case CK_FloatingCast: + case CK_FloatingRealToComplex: + case CK_FloatingComplexToReal: + case CK_FloatingComplexToBoolean: + case CK_FloatingComplexCast: + case CK_FloatingComplexToIntegralComplex: + case CK_IntegralRealToComplex: + case CK_IntegralComplexToReal: + case CK_IntegralComplexToBoolean: + case CK_IntegralComplexCast: + case CK_IntegralComplexToFloatingComplex: + case CK_DerivedToBaseMemberPointer: + case CK_BaseToDerivedMemberPointer: + case CK_MemberPointerToBoolean: + case CK_ReinterpretMemberPointer: + case CK_AnyPointerToBlockPointerCast: + case CK_ARCProduceObject: + case CK_ARCConsumeObject: + case CK_ARCReclaimReturnedObject: + case CK_ARCExtendBlockObject: + case CK_CopyAndAutoreleaseBlockObject: + case CK_IntToOCLSampler: + case CK_FloatingToFixedPoint: + case CK_FixedPointToFloating: + case CK_FixedPointCast: + case CK_FixedPointToBoolean: + case CK_FixedPointToIntegral: + case CK_IntegralToFixedPoint: + case CK_MatrixCast: + assert(0 && "NYI"); + + case CK_Dependent: + llvm_unreachable("dependent cast kind in IR gen!"); + + case CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + + // These are never l-values; just use the aggregate emission code. + case CK_NonAtomicToAtomic: + case CK_AtomicToNonAtomic: + assert(0 && "NYI"); + + case CK_Dynamic: { + assert(0 && "NYI"); + } + + case CK_ConstructorConversion: + case CK_UserDefinedConversion: + case CK_CPointerToObjCPointerCast: + case CK_BlockPointerToObjCPointerCast: + case CK_LValueToRValue: + assert(0 && "NYI"); + + case CK_NoOp: { + // CK_NoOp can model a qualification conversion, which can remove an array + // bound and change the IR type. + LValue LV = buildLValue(E->getSubExpr()); + if (LV.isSimple()) { + Address V = LV.getAddress(); + if (V.isValid()) { + auto T = getTypes().convertTypeForMem(E->getType()); + if (V.getElementType() != T) + LV.setAddress( + builder.createElementBitCast(getLoc(E->getSourceRange()), V, T)); + } + } + return LV; + } + + case CK_UncheckedDerivedToBase: + case CK_DerivedToBase: { + const auto *DerivedClassTy = + E->getSubExpr()->getType()->castAs(); + auto *DerivedClassDecl = cast(DerivedClassTy->getDecl()); + + LValue LV = buildLValue(E->getSubExpr()); + Address This = LV.getAddress(); + + // Perform the derived-to-base conversion + Address Base = getAddressOfBaseClass( + This, DerivedClassDecl, E->path_begin(), E->path_end(), + /*NullCheckValue=*/false, E->getExprLoc()); + + // TODO: Support accesses to members of base classes in TBAA. For now, we + // conservatively pretend that the complete object is of the base class + // type. + assert(!UnimplementedFeature::tbaa()); + return makeAddrLValue(Base, E->getType(), LV.getBaseInfo()); + } + case CK_ToUnion: + assert(0 && "NYI"); + case CK_BaseToDerived: { + assert(0 && "NYI"); + } + case CK_LValueBitCast: { + assert(0 && "NYI"); + } + case CK_AddressSpaceConversion: { + assert(0 && "NYI"); + } + case CK_ObjCObjectLValueCast: { + assert(0 && "NYI"); + } + case CK_ZeroToOCLOpaqueType: + llvm_unreachable("NULL to OpenCL opaque type lvalue cast is not valid"); + } + + llvm_unreachable("Unhandled lvalue cast kind?"); +} + +// TODO(cir): candidate for common helper between LLVM and CIR codegen. +static DeclRefExpr *tryToConvertMemberExprToDeclRefExpr(CIRGenFunction &CGF, + const MemberExpr *ME) { + if (auto *VD = dyn_cast(ME->getMemberDecl())) { + // Try to emit static variable member expressions as DREs. + return DeclRefExpr::Create( + CGF.getContext(), NestedNameSpecifierLoc(), SourceLocation(), VD, + /*RefersToEnclosingVariableOrCapture=*/false, ME->getExprLoc(), + ME->getType(), ME->getValueKind(), nullptr, nullptr, ME->isNonOdrUse()); + } + return nullptr; +} + +LValue CIRGenFunction::buildCheckedLValue(const Expr *E, TypeCheckKind TCK) { + LValue LV; + if (SanOpts.has(SanitizerKind::ArrayBounds) && isa(E)) + assert(0 && "not implemented"); + else + LV = buildLValue(E); + if (!isa(E) && !LV.isBitField() && LV.isSimple()) { + SanitizerSet SkippedChecks; + if (const auto *ME = dyn_cast(E)) { + bool IsBaseCXXThis = isWrappedCXXThis(ME->getBase()); + if (IsBaseCXXThis) + SkippedChecks.set(SanitizerKind::Alignment, true); + if (IsBaseCXXThis || isa(ME->getBase())) + SkippedChecks.set(SanitizerKind::Null, true); + } + buildTypeCheck(TCK, E->getExprLoc(), LV.getPointer(), E->getType(), + LV.getAlignment(), SkippedChecks); + } + return LV; +} + +// TODO(cir): candidate for common AST helper for LLVM and CIR codegen +bool CIRGenFunction::isWrappedCXXThis(const Expr *Obj) { + const Expr *Base = Obj; + while (!isa(Base)) { + // The result of a dynamic_cast can be null. + if (isa(Base)) + return false; + + if (const auto *CE = dyn_cast(Base)) { + Base = CE->getSubExpr(); + } else if (const auto *PE = dyn_cast(Base)) { + Base = PE->getSubExpr(); + } else if (const auto *UO = dyn_cast(Base)) { + if (UO->getOpcode() == UO_Extension) + Base = UO->getSubExpr(); + else + return false; + } else { + return false; + } + } + return true; +} + +LValue CIRGenFunction::buildMemberExpr(const MemberExpr *E) { + if (DeclRefExpr *DRE = tryToConvertMemberExprToDeclRefExpr(*this, E)) { + buildIgnoredExpr(E->getBase()); + return buildDeclRefLValue(DRE); + } + + Expr *BaseExpr = E->getBase(); + // If this is s.x, emit s as an lvalue. If it is s->x, emit s as a scalar. + LValue BaseLV; + if (E->isArrow()) { + LValueBaseInfo BaseInfo; + Address Addr = buildPointerWithAlignment(BaseExpr, &BaseInfo); + QualType PtrTy = BaseExpr->getType()->getPointeeType(); + SanitizerSet SkippedChecks; + bool IsBaseCXXThis = isWrappedCXXThis(BaseExpr); + if (IsBaseCXXThis) + SkippedChecks.set(SanitizerKind::Alignment, true); + if (IsBaseCXXThis || isa(BaseExpr)) + SkippedChecks.set(SanitizerKind::Null, true); + buildTypeCheck(TCK_MemberAccess, E->getExprLoc(), Addr.getPointer(), PtrTy, + /*Alignment=*/CharUnits::Zero(), SkippedChecks); + BaseLV = makeAddrLValue(Addr, PtrTy, BaseInfo); + } else + BaseLV = buildCheckedLValue(BaseExpr, TCK_MemberAccess); + + NamedDecl *ND = E->getMemberDecl(); + if (auto *Field = dyn_cast(ND)) { + LValue LV = buildLValueForField(BaseLV, Field); + assert(!UnimplementedFeature::setObjCGCLValueClass() && "NYI"); + if (getLangOpts().OpenMP) { + // If the member was explicitly marked as nontemporal, mark it as + // nontemporal. If the base lvalue is marked as nontemporal, mark access + // to children as nontemporal too. + assert(0 && "not implemented"); + } + return LV; + } + + if (const auto *FD = dyn_cast(ND)) + assert(0 && "not implemented"); + + llvm_unreachable("Unhandled member declaration!"); +} + +LValue CIRGenFunction::buildCallExprLValue(const CallExpr *E) { + RValue RV = buildCallExpr(E); + + if (!RV.isScalar()) + return makeAddrLValue(RV.getAggregateAddress(), E->getType(), + AlignmentSource::Decl); + + assert(E->getCallReturnType(getContext())->isReferenceType() && + "Can't have a scalar return unless the return type is a " + "reference type!"); + + return MakeNaturalAlignPointeeAddrLValue(RV.getScalarVal(), E->getType()); +} + +/// Evaluate an expression into a given memory location. +void CIRGenFunction::buildAnyExprToMem(const Expr *E, Address Location, + Qualifiers Quals, bool IsInit) { + // FIXME: This function should take an LValue as an argument. + switch (getEvaluationKind(E->getType())) { + case TEK_Complex: + assert(0 && "NYI"); + return; + + case TEK_Aggregate: { + buildAggExpr(E, AggValueSlot::forAddr(Location, Quals, + AggValueSlot::IsDestructed_t(IsInit), + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsAliased_t(!IsInit), + AggValueSlot::MayOverlap)); + return; + } + + case TEK_Scalar: { + RValue RV = RValue::get(buildScalarExpr(E)); + LValue LV = makeAddrLValue(Location, E->getType()); + buildStoreThroughLValue(RV, LV); + return; + } + } + llvm_unreachable("bad evaluation kind"); +} + +static Address createReferenceTemporary(CIRGenFunction &CGF, + const MaterializeTemporaryExpr *M, + const Expr *Inner, + Address *Alloca = nullptr) { + // TODO(cir): CGF.getTargetHooks(); + switch (M->getStorageDuration()) { + case SD_FullExpression: + case SD_Automatic: { + // TODO(cir): probably not needed / too LLVM specific? + // If we have a constant temporary array or record try to promote it into a + // constant global under the same rules a normal constant would've been + // promoted. This is easier on the optimizer and generally emits fewer + // instructions. + QualType Ty = Inner->getType(); + if (CGF.CGM.getCodeGenOpts().MergeAllConstants && + (Ty->isArrayType() || Ty->isRecordType()) && + CGF.CGM.isTypeConstant(Ty, /*ExcludeCtor=*/true, /*ExcludeDtor=*/false)) + assert(0 && "NYI"); + return CGF.CreateMemTemp(Ty, CGF.getLoc(M->getSourceRange()), + CGF.getCounterRefTmpAsString(), Alloca); + } + case SD_Thread: + case SD_Static: + assert(0 && "NYI"); + + case SD_Dynamic: + llvm_unreachable("temporary can't have dynamic storage duration"); + } + llvm_unreachable("unknown storage duration"); +} + +static void pushTemporaryCleanup(CIRGenFunction &CGF, + const MaterializeTemporaryExpr *M, + const Expr *E, Address ReferenceTemporary) { + // Objective-C++ ARC: + // If we are binding a reference to a temporary that has ownership, we + // need to perform retain/release operations on the temporary. + // + // FIXME: This should be looking at E, not M. + if (auto Lifetime = M->getType().getObjCLifetime()) { + assert(0 && "NYI"); + } + + CXXDestructorDecl *ReferenceTemporaryDtor = nullptr; + if (const RecordType *RT = + E->getType()->getBaseElementTypeUnsafe()->getAs()) { + // Get the destructor for the reference temporary. + auto *ClassDecl = cast(RT->getDecl()); + if (!ClassDecl->hasTrivialDestructor()) + ReferenceTemporaryDtor = ClassDecl->getDestructor(); + } + + if (!ReferenceTemporaryDtor) + return; + + // TODO(cir): Call the destructor for the temporary. + assert(0 && "NYI"); +} + +LValue CIRGenFunction::buildMaterializeTemporaryExpr( + const MaterializeTemporaryExpr *M) { + const Expr *E = M->getSubExpr(); + + assert((!M->getExtendingDecl() || !isa(M->getExtendingDecl()) || + !cast(M->getExtendingDecl())->isARCPseudoStrong()) && + "Reference should never be pseudo-strong!"); + + // FIXME: ideally this would use buildAnyExprToMem, however, we cannot do so + // as that will cause the lifetime adjustment to be lost for ARC + auto ownership = M->getType().getObjCLifetime(); + if (ownership != Qualifiers::OCL_None && + ownership != Qualifiers::OCL_ExplicitNone) { + assert(0 && "NYI"); + } + + SmallVector CommaLHSs; + SmallVector Adjustments; + E = E->skipRValueSubobjectAdjustments(CommaLHSs, Adjustments); + + for (const auto &Ignored : CommaLHSs) + buildIgnoredExpr(Ignored); + + if (const auto *opaque = dyn_cast(E)) + assert(0 && "NYI"); + + // Create and initialize the reference temporary. + Address Alloca = Address::invalid(); + Address Object = createReferenceTemporary(*this, M, E, &Alloca); + + if (auto Var = + dyn_cast(Object.getPointer().getDefiningOp())) { + // TODO(cir): add something akin to stripPointerCasts() to ptr above + assert(0 && "NYI"); + } else { + switch (M->getStorageDuration()) { + case SD_Automatic: + assert(0 && "NYI"); + break; + + case SD_FullExpression: { + if (!ShouldEmitLifetimeMarkers) + break; + assert(0 && "NYI"); + break; + } + + default: + break; + } + + buildAnyExprToMem(E, Object, Qualifiers(), /*IsInit*/ true); + } + pushTemporaryCleanup(*this, M, E, Object); + + // Perform derived-to-base casts and/or field accesses, to get from the + // temporary object we created (and, potentially, for which we extended + // the lifetime) to the subobject we're binding the reference to. + for (SubobjectAdjustment &Adjustment : llvm::reverse(Adjustments)) { + (void)Adjustment; + assert(0 && "NYI"); + } + + return makeAddrLValue(Object, M->getType(), AlignmentSource::Decl); +} + +LValue CIRGenFunction::buildOpaqueValueLValue(const OpaqueValueExpr *e) { + assert(OpaqueValueMappingData::shouldBindAsLValue(e)); + return getOrCreateOpaqueLValueMapping(e); +} + +LValue +CIRGenFunction::getOrCreateOpaqueLValueMapping(const OpaqueValueExpr *e) { + assert(OpaqueValueMapping::shouldBindAsLValue(e)); + + llvm::DenseMap::iterator it = + OpaqueLValues.find(e); + + if (it != OpaqueLValues.end()) + return it->second; + + assert(e->isUnique() && "LValue for a nonunique OVE hasn't been emitted"); + return buildLValue(e->getSourceExpr()); +} + +RValue +CIRGenFunction::getOrCreateOpaqueRValueMapping(const OpaqueValueExpr *e) { + assert(!OpaqueValueMapping::shouldBindAsLValue(e)); + + llvm::DenseMap::iterator it = + OpaqueRValues.find(e); + + if (it != OpaqueRValues.end()) + return it->second; + + assert(e->isUnique() && "RValue for a nonunique OVE hasn't been emitted"); + return buildAnyExpr(e->getSourceExpr()); +} + +namespace { +// Handle the case where the condition is a constant evaluatable simple integer, +// which means we don't have to separately handle the true/false blocks. +std::optional HandleConditionalOperatorLValueSimpleCase( + CIRGenFunction &CGF, const AbstractConditionalOperator *E) { + const Expr *condExpr = E->getCond(); + bool CondExprBool; + if (CGF.ConstantFoldsToSimpleInteger(condExpr, CondExprBool)) { + const Expr *Live = E->getTrueExpr(), *Dead = E->getFalseExpr(); + if (!CondExprBool) + std::swap(Live, Dead); + + if (!CGF.ContainsLabel(Dead)) { + // If the true case is live, we need to track its region. + if (CondExprBool) { + assert(!UnimplementedFeature::incrementProfileCounter()); + } + // If a throw expression we emit it and return an undefined lvalue + // because it can't be used. + if (auto *ThrowExpr = dyn_cast(Live->IgnoreParens())) { + llvm_unreachable("NYI"); + } + return CGF.buildLValue(Live); + } + } + return std::nullopt; +} +} // namespace + +/// Emit the operand of a glvalue conditional operator. This is either a glvalue +/// or a (possibly-parenthesized) throw-expression. If this is a throw, no +/// LValue is returned and the current block has been terminated. +static std::optional buildLValueOrThrowExpression(CIRGenFunction &CGF, + const Expr *Operand) { + if (auto *ThrowExpr = dyn_cast(Operand->IgnoreParens())) { + llvm_unreachable("NYI"); + } + + return CGF.buildLValue(Operand); +} + +// Create and generate the 3 blocks for a conditional operator. +// Leaves the 'current block' in the continuation basic block. +template +CIRGenFunction::ConditionalInfo +CIRGenFunction::buildConditionalBlocks(const AbstractConditionalOperator *E, + const FuncTy &BranchGenFunc) { + ConditionalInfo Info; + auto &CGF = *this; + ConditionalEvaluation eval(CGF); + auto loc = CGF.getLoc(E->getSourceRange()); + auto &builder = CGF.getBuilder(); + auto *trueExpr = E->getTrueExpr(); + auto *falseExpr = E->getFalseExpr(); + + mlir::Value condV = + CGF.buildOpOnBoolExpr(E->getCond(), loc, trueExpr, falseExpr); + SmallVector insertPoints{}; + mlir::Type yieldTy{}; + + auto patchVoidOrThrowSites = [&]() { + if (insertPoints.empty()) + return; + // If both arms are void, so be it. + if (!yieldTy) + yieldTy = CGF.VoidTy; + + // Insert required yields. + for (auto &toInsert : insertPoints) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(toInsert); + + // Block does not return: build empty yield. + if (yieldTy.isa()) { + builder.create(loc); + } else { // Block returns: set null yield value. + mlir::Value op0 = builder.getNullValue(yieldTy, loc); + builder.create(loc, op0); + } + } + }; + + Info.Result = + builder + .create( + loc, condV, /*trueBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + loc, b.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexThenGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + + assert(!UnimplementedFeature::incrementProfileCounter()); + eval.begin(CGF); + Info.LHS = BranchGenFunc(CGF, trueExpr); + auto lhs = Info.LHS->getPointer(); + eval.end(CGF); + + if (lhs) { + yieldTy = lhs.getType(); + b.create(loc, lhs); + return; + } + // If LHS or RHS is a throw or void expression we need to patch + // arms as to properly match yield types. + insertPoints.push_back(b.saveInsertionPoint()); + }, + /*falseBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + loc, b.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + + assert(!UnimplementedFeature::incrementProfileCounter()); + eval.begin(CGF); + Info.RHS = BranchGenFunc(CGF, falseExpr); + auto rhs = Info.RHS->getPointer(); + eval.end(CGF); + + if (rhs) { + yieldTy = rhs.getType(); + b.create(loc, rhs); + } else { + // If LHS or RHS is a throw or void expression we need to + // patch arms as to properly match yield types. + insertPoints.push_back(b.saveInsertionPoint()); + } + + patchVoidOrThrowSites(); + }) + .getResult(); + return Info; +} + +LValue CIRGenFunction::buildConditionalOperatorLValue( + const AbstractConditionalOperator *expr) { + if (!expr->isGLValue()) { + llvm_unreachable("NYI"); + } + + OpaqueValueMapping binding(*this, expr); + if (std::optional Res = + HandleConditionalOperatorLValueSimpleCase(*this, expr)) + return *Res; + + ConditionalInfo Info = + buildConditionalBlocks(expr, [](CIRGenFunction &CGF, const Expr *E) { + return buildLValueOrThrowExpression(CGF, E); + }); + + if ((Info.LHS && !Info.LHS->isSimple()) || + (Info.RHS && !Info.RHS->isSimple())) + llvm_unreachable("unsupported conditional operator"); + + if (Info.LHS && Info.RHS) { + Address lhsAddr = Info.LHS->getAddress(); + Address rhsAddr = Info.RHS->getAddress(); + Address result(Info.Result, lhsAddr.getElementType(), + std::min(lhsAddr.getAlignment(), rhsAddr.getAlignment())); + AlignmentSource alignSource = + std::max(Info.LHS->getBaseInfo().getAlignmentSource(), + Info.RHS->getBaseInfo().getAlignmentSource()); + assert(!UnimplementedFeature::tbaa()); + return makeAddrLValue(result, expr->getType(), LValueBaseInfo(alignSource)); + } else { + llvm_unreachable("NYI"); + } +} + +/// Emit code to compute a designator that specifies the location +/// of the expression. +/// FIXME: document this function better. +LValue CIRGenFunction::buildLValue(const Expr *E) { + // FIXME: ApplyDebugLocation DL(*this, E); + switch (E->getStmtClass()) { + default: { + emitError(getLoc(E->getExprLoc()), "l-value not implemented for '") + << E->getStmtClassName() << "'"; + assert(0 && "not implemented"); + } + case Expr::ConditionalOperatorClass: + return buildConditionalOperatorLValue(cast(E)); + case Expr::ArraySubscriptExprClass: + return buildArraySubscriptExpr(cast(E)); + case Expr::BinaryOperatorClass: + return buildBinaryOperatorLValue(cast(E)); + case Expr::CompoundAssignOperatorClass: { + QualType Ty = E->getType(); + if (const AtomicType *AT = Ty->getAs()) + assert(0 && "not yet implemented"); + assert(!Ty->isAnyComplexType() && "complex types not implemented"); + return buildCompoundAssignmentLValue(cast(E)); + } + case Expr::CallExprClass: + case Expr::CXXMemberCallExprClass: + case Expr::CXXOperatorCallExprClass: + case Expr::UserDefinedLiteralClass: + return buildCallExprLValue(cast(E)); + case Expr::ExprWithCleanupsClass: { + const auto *cleanups = cast(E); + LValue LV; + + auto scopeLoc = getLoc(E->getSourceRange()); + [[maybe_unused]] auto scope = builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + loc, builder.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexScopeGuard{*this, &lexScope}; + + LV = buildLValue(cleanups->getSubExpr()); + if (LV.isSimple()) { + // Defend against branches out of gnu statement expressions + // surrounded by cleanups. + Address Addr = LV.getAddress(); + auto V = Addr.getPointer(); + LV = LValue::makeAddr(Addr.withPointer(V, NotKnownNonNull), + LV.getType(), getContext(), + LV.getBaseInfo() /*TODO(cir):TBAA*/); + } + }); + + // FIXME: Is it possible to create an ExprWithCleanups that produces a + // bitfield lvalue or some other non-simple lvalue? + return LV; + } + case Expr::ParenExprClass: + return buildLValue(cast(E)->getSubExpr()); + case Expr::DeclRefExprClass: + return buildDeclRefLValue(cast(E)); + case Expr::UnaryOperatorClass: + return buildUnaryOpLValue(cast(E)); + case Expr::StringLiteralClass: + return buildStringLiteralLValue(cast(E)); + case Expr::MemberExprClass: + return buildMemberExpr(cast(E)); + case Expr::PredefinedExprClass: + return buildPredefinedLValue(cast(E)); + case Expr::CStyleCastExprClass: + case Expr::CXXFunctionalCastExprClass: + case Expr::CXXDynamicCastExprClass: + case Expr::CXXReinterpretCastExprClass: + case Expr::CXXConstCastExprClass: + case Expr::CXXAddrspaceCastExprClass: + case Expr::ObjCBridgedCastExprClass: + emitError(getLoc(E->getExprLoc()), "l-value not implemented for '") + << E->getStmtClassName() << "'"; + assert(0 && "Use buildCastLValue below, remove me when adding testcase"); + case Expr::CXXStaticCastExprClass: + case Expr::ImplicitCastExprClass: + return buildCastLValue(cast(E)); + case Expr::OpaqueValueExprClass: + return buildOpaqueValueLValue(cast(E)); + + case Expr::MaterializeTemporaryExprClass: + return buildMaterializeTemporaryExpr(cast(E)); + + case Expr::ObjCPropertyRefExprClass: + llvm_unreachable("cannot emit a property reference directly"); + } + + return LValue::makeAddr(Address::invalid(), E->getType()); +} + +/// Given the address of a temporary variable, produce an r-value of its type. +RValue CIRGenFunction::convertTempToRValue(Address addr, clang::QualType type, + clang::SourceLocation loc) { + LValue lvalue = makeAddrLValue(addr, type, AlignmentSource::Decl); + switch (getEvaluationKind(type)) { + case TEK_Complex: + llvm_unreachable("NYI"); + case TEK_Aggregate: + llvm_unreachable("NYI"); + case TEK_Scalar: + return RValue::get(buildLoadOfScalar(lvalue, loc)); + } + llvm_unreachable("NYI"); +} + +/// An LValue is a candidate for having its loads and stores be made atomic if +/// we are operating under /volatile:ms *and* the LValue itself is volatile and +/// performing such an operation can be performed without a libcall. +bool CIRGenFunction::LValueIsSuitableForInlineAtomic(LValue LV) { + if (!CGM.getLangOpts().MSVolatile) + return false; + + llvm_unreachable("NYI"); +} + +/// Emit an `if` on a boolean condition, filling `then` and `else` into +/// appropriated regions. +mlir::LogicalResult CIRGenFunction::buildIfOnBoolExpr(const Expr *cond, + const Stmt *thenS, + const Stmt *elseS) { + auto getStmtLoc = [this](const Stmt &s) { + return mlir::FusedLoc::get(builder.getContext(), + {getLoc(s.getSourceRange().getBegin()), + getLoc(s.getSourceRange().getEnd())}); + }; + + auto thenLoc = getStmtLoc(*thenS); + std::optional elseLoc; + SmallVector ifLocs{thenLoc}; + + if (elseS) { + elseLoc = getStmtLoc(*elseS); + ifLocs.push_back(*elseLoc); + } + + // Attempt to be more accurate as possible with IfOp location, generate + // one fused location that has either 2 or 4 total locations, depending + // on else's availability. + auto loc = mlir::FusedLoc::get(builder.getContext(), ifLocs); + + // Emit the code with the fully general case. + mlir::Value condV = buildOpOnBoolExpr(cond, loc, thenS, elseS); + mlir::LogicalResult resThen = mlir::success(), resElse = mlir::success(); + + builder.create( + loc, condV, elseS, + /*thenBuilder=*/ + [&](mlir::OpBuilder &, mlir::Location) { + LexicalScopeContext lexScope{thenLoc, builder.getInsertionBlock()}; + LexicalScopeGuard lexThenGuard{*this, &lexScope}; + resThen = buildStmt(thenS, /*useCurrentScope=*/true); + }, + /*elseBuilder=*/ + [&](mlir::OpBuilder &, mlir::Location) { + assert(elseLoc && "Invalid location for elseS."); + LexicalScopeContext lexScope{*elseLoc, builder.getInsertionBlock()}; + LexicalScopeGuard lexElseGuard{*this, &lexScope}; + resElse = buildStmt(elseS, /*useCurrentScope=*/true); + }); + + return mlir::LogicalResult::success(resThen.succeeded() && + resElse.succeeded()); +} + +/// TODO(cir): PGO data +/// TODO(cir): see EmitBranchOnBoolExpr for extra ideas). +mlir::Value CIRGenFunction::buildOpOnBoolExpr(const Expr *cond, + mlir::Location loc, + const Stmt *thenS, + const Stmt *elseS) { + // TODO(CIR): scoped ApplyDebugLocation DL(*this, Cond); + // TODO(CIR): __builtin_unpredictable and profile counts? + cond = cond->IgnoreParens(); + + // if (const BinaryOperator *CondBOp = dyn_cast(cond)) { + // llvm_unreachable("binaryoperator ifstmt NYI"); + // } + + if (const UnaryOperator *CondUOp = dyn_cast(cond)) { + // In LLVM the condition is reversed here for efficient codegen. + // This should be done in CIR prior to LLVM lowering, if we do now + // we can make CIR based diagnostics misleading. + // cir.ternary(!x, t, f) -> cir.ternary(x, f, t) + // if (CondUOp->getOpcode() == UO_LNot) { + // buildOpOnBoolExpr(CondUOp->getSubExpr(), loc, elseS, thenS); + // } + assert(!UnimplementedFeature::shouldReverseUnaryCondOnBoolExpr()); + } + + if (const ConditionalOperator *CondOp = dyn_cast(cond)) { + llvm_unreachable("NYI"); + } + + if (const CXXThrowExpr *Throw = dyn_cast(cond)) { + llvm_unreachable("NYI"); + } + + // If the branch has a condition wrapped by __builtin_unpredictable, + // create metadata that specifies that the branch is unpredictable. + // Don't bother if not optimizing because that metadata would not be used. + auto *Call = dyn_cast(cond->IgnoreImpCasts()); + if (Call && CGM.getCodeGenOpts().OptimizationLevel != 0) { + assert(!UnimplementedFeature::insertBuiltinUnpredictable()); + } + + // Emit the code with the fully general case. + return evaluateExprAsBool(cond); +} + +mlir::Value CIRGenFunction::buildAlloca(StringRef name, mlir::Type ty, + mlir::Location loc, CharUnits alignment, + bool insertIntoFnEntryBlock) { + mlir::Block *entryBlock = insertIntoFnEntryBlock + ? getCurFunctionEntryBlock() + : currLexScope->getEntryBlock(); + return buildAlloca(name, ty, loc, alignment, + builder.getBestAllocaInsertPoint(entryBlock)); +} + +mlir::Value CIRGenFunction::buildAlloca(StringRef name, mlir::Type ty, + mlir::Location loc, CharUnits alignment, + mlir::OpBuilder::InsertPoint ip) { + auto localVarPtrTy = mlir::cir::PointerType::get(builder.getContext(), ty); + auto alignIntAttr = CGM.getSize(alignment); + + mlir::Value addr; + { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(ip); + addr = builder.create(loc, /*addr type*/ localVarPtrTy, + /*var type*/ ty, name, + alignIntAttr); + if (currVarDecl) { + auto alloca = cast(addr.getDefiningOp()); + alloca.setAstAttr(ASTVarDeclAttr::get(builder.getContext(), currVarDecl)); + } + } + return addr; +} + +mlir::Value CIRGenFunction::buildAlloca(StringRef name, QualType ty, + mlir::Location loc, CharUnits alignment, + bool insertIntoFnEntryBlock) { + return buildAlloca(name, getCIRType(ty), loc, alignment, + insertIntoFnEntryBlock); +} + +mlir::Value CIRGenFunction::buildLoadOfScalar(LValue lvalue, + SourceLocation Loc) { + return buildLoadOfScalar(lvalue.getAddress(), lvalue.isVolatile(), + lvalue.getType(), getLoc(Loc), lvalue.getBaseInfo(), + lvalue.isNontemporal()); +} + +mlir::Value CIRGenFunction::buildLoadOfScalar(LValue lvalue, + mlir::Location Loc) { + return buildLoadOfScalar(lvalue.getAddress(), lvalue.isVolatile(), + lvalue.getType(), Loc, lvalue.getBaseInfo(), + lvalue.isNontemporal()); +} + +mlir::Value CIRGenFunction::buildFromMemory(mlir::Value Value, QualType Ty) { + if (!Ty->isBooleanType() && hasBooleanRepresentation(Ty)) { + llvm_unreachable("NIY"); + } + + return Value; +} + +mlir::Value CIRGenFunction::buildLoadOfScalar(Address Addr, bool Volatile, + QualType Ty, SourceLocation Loc, + LValueBaseInfo BaseInfo, + bool isNontemporal) { + return buildLoadOfScalar(Addr, Volatile, Ty, getLoc(Loc), BaseInfo, + isNontemporal); +} + +mlir::Value CIRGenFunction::buildLoadOfScalar(Address Addr, bool Volatile, + QualType Ty, mlir::Location Loc, + LValueBaseInfo BaseInfo, + bool isNontemporal) { + if (!CGM.getCodeGenOpts().PreserveVec3Type) { + if (Ty->isVectorType()) { + llvm_unreachable("NYI"); + } + } + + // Atomic operations have to be done on integral types + LValue AtomicLValue = LValue::makeAddr(Addr, Ty, getContext(), BaseInfo); + if (Ty->isAtomicType() || LValueIsSuitableForInlineAtomic(AtomicLValue)) { + llvm_unreachable("NYI"); + } + + mlir::cir::LoadOp Load = builder.create( + Loc, Addr.getElementType(), Addr.getPointer()); + + if (isNontemporal) { + llvm_unreachable("NYI"); + } + + assert(!UnimplementedFeature::tbaa() && "NYI"); + assert(!UnimplementedFeature::emitScalarRangeCheck() && "NYI"); + + return buildFromMemory(Load, Ty); +} + +// Note: this function also emit constructor calls to support a MSVC extensions +// allowing explicit constructor function call. +RValue CIRGenFunction::buildCXXMemberCallExpr(const CXXMemberCallExpr *CE, + ReturnValueSlot ReturnValue) { + + const Expr *callee = CE->getCallee()->IgnoreParens(); + + if (isa(callee)) + llvm_unreachable("NYI"); + + const auto *ME = cast(callee); + const auto *MD = cast(ME->getMemberDecl()); + + if (MD->isStatic()) { + llvm_unreachable("NYI"); + } + + bool HasQualifier = ME->hasQualifier(); + NestedNameSpecifier *Qualifier = HasQualifier ? ME->getQualifier() : nullptr; + bool IsArrow = ME->isArrow(); + const Expr *Base = ME->getBase(); + + return buildCXXMemberOrOperatorMemberCallExpr( + CE, MD, ReturnValue, HasQualifier, Qualifier, IsArrow, Base); +} + +RValue CIRGenFunction::buildReferenceBindingToExpr(const Expr *E) { + // Emit the expression as an lvalue. + LValue LV = buildLValue(E); + assert(LV.isSimple()); + auto Value = LV.getPointer(); + + if (sanitizePerformTypeCheck() && !E->getType()->isFunctionType()) { + assert(0 && "NYI"); + } + + return RValue::get(Value); +} + +Address CIRGenFunction::buildLoadOfReference(LValue RefLVal, mlir::Location Loc, + LValueBaseInfo *PointeeBaseInfo) { + assert(!RefLVal.isVolatile() && "NYI"); + mlir::cir::LoadOp Load = builder.create( + Loc, RefLVal.getAddress().getElementType(), + RefLVal.getAddress().getPointer()); + + // TODO(cir): DecorateInstructionWithTBAA relevant for us? + assert(!UnimplementedFeature::tbaa()); + + QualType PointeeType = RefLVal.getType()->getPointeeType(); + CharUnits Align = CGM.getNaturalTypeAlignment(PointeeType, PointeeBaseInfo, + /* forPointeeType= */ true); + return Address(Load, getTypes().convertTypeForMem(PointeeType), Align); +} + +LValue CIRGenFunction::buildLoadOfReferenceLValue(LValue RefLVal, + mlir::Location Loc) { + LValueBaseInfo PointeeBaseInfo; + Address PointeeAddr = buildLoadOfReference(RefLVal, Loc, &PointeeBaseInfo); + return makeAddrLValue(PointeeAddr, RefLVal.getType()->getPointeeType(), + PointeeBaseInfo); +} + +//===----------------------------------------------------------------------===// +// CIR builder helpers +//===----------------------------------------------------------------------===// + +Address CIRGenFunction::CreateMemTemp(QualType Ty, mlir::Location Loc, + const Twine &Name, Address *Alloca) { + // FIXME: Should we prefer the preferred type alignment here? + return CreateMemTemp(Ty, getContext().getTypeAlignInChars(Ty), Loc, Name, + Alloca); +} + +Address CIRGenFunction::CreateMemTemp(QualType Ty, CharUnits Align, + mlir::Location Loc, const Twine &Name, + Address *Alloca) { + Address Result = + CreateTempAlloca(getTypes().convertTypeForMem(Ty), Align, Loc, Name, + /*ArraySize=*/nullptr, Alloca); + if (Ty->isConstantMatrixType()) { + assert(0 && "NYI"); + } + return Result; +} + +/// This creates a alloca and inserts it into the entry block of the +/// current region. +Address CIRGenFunction::CreateTempAllocaWithoutCast(mlir::Type Ty, + CharUnits Align, + mlir::Location Loc, + const Twine &Name, + mlir::Value ArraySize) { + auto Alloca = CreateTempAlloca(Ty, Loc, Name, ArraySize); + Alloca.setAlignmentAttr(CGM.getSize(Align)); + return Address(Alloca, Ty, Align); +} + +/// This creates a alloca and inserts it into the entry block. The alloca is +/// casted to default address space if necessary. +Address CIRGenFunction::CreateTempAlloca(mlir::Type Ty, CharUnits Align, + mlir::Location Loc, const Twine &Name, + mlir::Value ArraySize, + Address *AllocaAddr) { + auto Alloca = CreateTempAllocaWithoutCast(Ty, Align, Loc, Name, ArraySize); + if (AllocaAddr) + *AllocaAddr = Alloca; + mlir::Value V = Alloca.getPointer(); + // Alloca always returns a pointer in alloca address space, which may + // be different from the type defined by the language. For example, + // in C++ the auto variables are in the default address space. Therefore + // cast alloca to the default address space when necessary. + assert(!UnimplementedFeature::getASTAllocaAddressSpace()); + return Address(V, Ty, Align); +} + +/// This creates an alloca and inserts it into the entry block if \p ArraySize +/// is nullptr, otherwise inserts it at the current insertion point of the +/// builder. +mlir::cir::AllocaOp +CIRGenFunction::CreateTempAlloca(mlir::Type Ty, mlir::Location Loc, + const Twine &Name, mlir::Value ArraySize, + bool insertIntoFnEntryBlock) { + if (ArraySize) + assert(0 && "NYI"); + return cast( + buildAlloca(Name.str(), Ty, Loc, CharUnits(), insertIntoFnEntryBlock) + .getDefiningOp()); +} + +/// Just like CreateTempAlloca above, but place the alloca into the function +/// entry basic block instead. +mlir::cir::AllocaOp CIRGenFunction::CreateTempAllocaInFnEntryBlock( + mlir::Type Ty, mlir::Location Loc, const Twine &Name, + mlir::Value ArraySize) { + return CreateTempAlloca(Ty, Loc, Name, ArraySize, + /*insertIntoFnEntryBlock=*/true); +} + +/// Given an object of the given canonical type, can we safely copy a +/// value out of it based on its initializer? +static bool isConstantEmittableObjectType(QualType type) { + assert(type.isCanonical()); + assert(!type->isReferenceType()); + + // Must be const-qualified but non-volatile. + Qualifiers qs = type.getLocalQualifiers(); + if (!qs.hasConst() || qs.hasVolatile()) + return false; + + // Otherwise, all object types satisfy this except C++ classes with + // mutable subobjects or non-trivial copy/destroy behavior. + if (const auto *RT = dyn_cast(type)) + if (const auto *RD = dyn_cast(RT->getDecl())) + if (RD->hasMutableFields() || !RD->isTrivial()) + return false; + + return true; +} + +/// Can we constant-emit a load of a reference to a variable of the +/// given type? This is different from predicates like +/// Decl::mightBeUsableInConstantExpressions because we do want it to apply +/// in situations that don't necessarily satisfy the language's rules +/// for this (e.g. C++'s ODR-use rules). For example, we want to able +/// to do this with const float variables even if those variables +/// aren't marked 'constexpr'. +enum ConstantEmissionKind { + CEK_None, + CEK_AsReferenceOnly, + CEK_AsValueOrReference, + CEK_AsValueOnly +}; +static ConstantEmissionKind checkVarTypeForConstantEmission(QualType type) { + type = type.getCanonicalType(); + if (const auto *ref = dyn_cast(type)) { + if (isConstantEmittableObjectType(ref->getPointeeType())) + return CEK_AsValueOrReference; + return CEK_AsReferenceOnly; + } + if (isConstantEmittableObjectType(type)) + return CEK_AsValueOnly; + return CEK_None; +} + +/// Try to emit a reference to the given value without producing it as +/// an l-value. This is just an optimization, but it avoids us needing +/// to emit global copies of variables if they're named without triggering +/// a formal use in a context where we can't emit a direct reference to them, +/// for instance if a block or lambda or a member of a local class uses a +/// const int variable or constexpr variable from an enclosing function. +CIRGenFunction::ConstantEmission +CIRGenFunction::tryEmitAsConstant(DeclRefExpr *refExpr) { + ValueDecl *value = refExpr->getDecl(); + + // The value needs to be an enum constant or a constant variable. + ConstantEmissionKind CEK; + if (isa(value)) { + CEK = CEK_None; + } else if (auto *var = dyn_cast(value)) { + CEK = checkVarTypeForConstantEmission(var->getType()); + } else if (isa(value)) { + CEK = CEK_AsValueOnly; + } else { + CEK = CEK_None; + } + if (CEK == CEK_None) + return ConstantEmission(); + + Expr::EvalResult result; + bool resultIsReference; + QualType resultType; + + // It's best to evaluate all the way as an r-value if that's permitted. + if (CEK != CEK_AsReferenceOnly && + refExpr->EvaluateAsRValue(result, getContext())) { + resultIsReference = false; + resultType = refExpr->getType(); + + // Otherwise, try to evaluate as an l-value. + } else if (CEK != CEK_AsValueOnly && + refExpr->EvaluateAsLValue(result, getContext())) { + resultIsReference = true; + resultType = value->getType(); + + // Failure. + } else { + return ConstantEmission(); + } + + // In any case, if the initializer has side-effects, abandon ship. + if (result.HasSideEffects) + return ConstantEmission(); + + // In CUDA/HIP device compilation, a lambda may capture a reference variable + // referencing a global host variable by copy. In this case the lambda should + // make a copy of the value of the global host variable. The DRE of the + // captured reference variable cannot be emitted as load from the host + // global variable as compile time constant, since the host variable is not + // accessible on device. The DRE of the captured reference variable has to be + // loaded from captures. + if (CGM.getLangOpts().CUDAIsDevice && result.Val.isLValue() && + refExpr->refersToEnclosingVariableOrCapture()) { + auto *MD = dyn_cast_or_null(CurCodeDecl); + if (MD && MD->getParent()->isLambda() && + MD->getOverloadedOperator() == OO_Call) { + const APValue::LValueBase &base = result.Val.getLValueBase(); + if (const ValueDecl *D = base.dyn_cast()) { + if (const VarDecl *VD = dyn_cast(D)) { + if (!VD->hasAttr()) { + return ConstantEmission(); + } + } + } + } + } + + // Emit as a constant. + // FIXME(cir): have emitAbstract build a TypedAttr instead (this requires + // somewhat heavy refactoring...) + auto C = ConstantEmitter(*this).emitAbstract(refExpr->getLocation(), + result.Val, resultType); + mlir::TypedAttr cstToEmit = C.dyn_cast_or_null(); + assert(cstToEmit && "expect a typed attribute"); + + // Make sure we emit a debug reference to the global variable. + // This should probably fire even for + if (isa(value)) { + if (!getContext().DeclMustBeEmitted(cast(value))) + buildDeclRefExprDbgValue(refExpr, result.Val); + } else { + assert(isa(value)); + buildDeclRefExprDbgValue(refExpr, result.Val); + } + + // If we emitted a reference constant, we need to dereference that. + if (resultIsReference) + return ConstantEmission::forReference(cstToEmit); + + return ConstantEmission::forValue(cstToEmit); +} + +CIRGenFunction::ConstantEmission +CIRGenFunction::tryEmitAsConstant(const MemberExpr *ME) { + llvm_unreachable("NYI"); +} + +mlir::Value CIRGenFunction::buildScalarConstant( + const CIRGenFunction::ConstantEmission &Constant, Expr *E) { + assert(Constant && "not a constant"); + if (Constant.isReference()) + return buildLoadOfLValue(Constant.getReferenceLValue(*this, E), + E->getExprLoc()) + .getScalarVal(); + return builder.getConstant(getLoc(E->getSourceRange()), Constant.getValue()); +} + +LValue CIRGenFunction::buildPredefinedLValue(const PredefinedExpr *E) { + auto SL = E->getFunctionName(); + assert(SL != nullptr && "No StringLiteral name in PredefinedExpr"); + auto Fn = dyn_cast(CurFn); + assert(Fn && "other callables NYI"); + StringRef FnName = Fn.getName(); + if (FnName.startswith("\01")) + FnName = FnName.substr(1); + StringRef NameItems[] = {PredefinedExpr::getIdentKindName(E->getIdentKind()), + FnName}; + std::string GVName = llvm::join(NameItems, NameItems + 2, "."); + if (auto *BD = dyn_cast_or_null(CurCodeDecl)) { + llvm_unreachable("NYI"); + } + + return buildStringLiteralLValue(SL); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp b/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp new file mode 100644 index 000000000000..761537534409 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExprAgg.cpp @@ -0,0 +1,1144 @@ +//===--- CIRGenExprAgg.cpp - Emit CIR Code from Aggregate Expressions -----===// +// +// 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 Aggregate Expr nodes as CIR code. +// +//===----------------------------------------------------------------------===// +#include "CIRGenCall.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "CIRGenTypes.h" +#include "CIRGenValue.h" +#include "UnimplementedFeatureGuarding.h" +#include "mlir/IR/Attributes.h" + +#include "clang/AST/Decl.h" +#include "clang/AST/OperationKinds.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/raw_ostream.h" + +using namespace cir; +using namespace clang; + +namespace { +class AggExprEmitter : public StmtVisitor { + CIRGenFunction &CGF; + AggValueSlot Dest; + bool IsResultUnused; + + // Calls `Fn` with a valid return value slot, potentially creating a temporary + // to do so. If a temporary is created, an appropriate copy into `Dest` will + // be emitted, as will lifetime markers. + // + // The given function should take a ReturnValueSlot, and return an RValue that + // points to said slot. + void withReturnValueSlot(const Expr *E, + llvm::function_ref Fn); + + AggValueSlot EnsureSlot(QualType T) { + assert(!Dest.isIgnored() && "ignored slots NYI"); + return Dest; + } + + void EnsureDest(mlir::Location loc, QualType T) { + if (!Dest.isIgnored()) + return; + Dest = CGF.CreateAggTemp(T, loc, "agg.tmp.ensured"); + } + +public: + AggExprEmitter(CIRGenFunction &cgf, AggValueSlot Dest, bool IsResultUnused) + : CGF{cgf}, Dest(Dest), IsResultUnused(IsResultUnused) {} + + //===--------------------------------------------------------------------===// + // Utilities + //===--------------------------------------------------------------------===// + + /// Given an expression with aggregate type that represents a value lvalue, + /// this method emits the address of the lvalue, then loads the result into + /// DestPtr. + void buildAggLoadOfLValue(const Expr *E); + + enum ExprValueKind { EVK_RValue, EVK_NonRValue }; + + /// Perform the final copy to DestPtr, if desired. SrcIsRValue is true if + /// source comes from an RValue. + void buildFinalDestCopy(QualType type, const LValue &src, + ExprValueKind SrcValueKind = EVK_NonRValue); + void buildCopy(QualType type, const AggValueSlot &dest, + const AggValueSlot &src); + + AggValueSlot::NeedsGCBarriers_t needsGC(QualType T) { + if (CGF.getLangOpts().getGC() && TypeRequiresGCollection(T)) + llvm_unreachable("garbage collection is NYI"); + return AggValueSlot::DoesNotNeedGCBarriers; + } + + bool TypeRequiresGCollection(QualType T); + + //===--------------------------------------------------------------------===// + // Visitor Methods + //===--------------------------------------------------------------------===// + + void Visit(Expr *E) { + if (CGF.getDebugInfo()) { + llvm_unreachable("NYI"); + } + StmtVisitor::Visit(E); + } + + void VisitStmt(Stmt *S) { + llvm::errs() << "Missing visitor for AggExprEmitter Stmt: " + << S->getStmtClassName() << "\n"; + llvm_unreachable("NYI"); + } + void VisitParenExpr(ParenExpr *PE) { llvm_unreachable("NYI"); } + void VisitGenericSelectionExpr(GenericSelectionExpr *GE) { + llvm_unreachable("NYI"); + } + void VisitCoawaitExpr(CoawaitExpr *E) { + CGF.buildCoawaitExpr(*E, Dest, IsResultUnused); + } + void VisitCoyieldExpr(CoyieldExpr *E) { llvm_unreachable("NYI"); } + void VisitUnaryCoawait(UnaryOperator *E) { llvm_unreachable("NYI"); } + void VisitUnaryExtension(UnaryOperator *E) { llvm_unreachable("NYI"); } + void VisitSubstNonTypeTemplateParmExpr(SubstNonTypeTemplateParmExpr *E) { + llvm_unreachable("NYI"); + } + void VisitConstantExpr(ConstantExpr *E) { llvm_unreachable("NYI"); } + + // l-values + void VisitDeclRefExpr(DeclRefExpr *E) { buildAggLoadOfLValue(E); } + void VisitMemberExpr(MemberExpr *E) { llvm_unreachable("NYI"); } + void VisitUnaryDeref(UnaryOperator *E) { llvm_unreachable("NYI"); } + void VisitStringLiteral(StringLiteral *E) { llvm_unreachable("NYI"); } + void VisitCompoundLIteralExpr(CompoundLiteralExpr *E) { + llvm_unreachable("NYI"); + } + void VisitArraySubscriptExpr(ArraySubscriptExpr *E) { + buildAggLoadOfLValue(E); + } + void VisitPredefinedExpr(const PredefinedExpr *E) { llvm_unreachable("NYI"); } + + // Operators. + void VisitCastExpr(CastExpr *E); + void VisitCallExpr(const CallExpr *E); + void VisitStmtExpr(const StmtExpr *E) { llvm_unreachable("NYI"); } + void VisitBinaryOperator(const BinaryOperator *E) { llvm_unreachable("NYI"); } + void VisitPointerToDataMemberBinaryOperator(const BinaryOperator *E) { + llvm_unreachable("NYI"); + } + void VisitBinAssign(const BinaryOperator *E) { llvm_unreachable("NYI"); } + void VisitBinComma(const BinaryOperator *E) { llvm_unreachable("NYI"); } + void VisitBinCmp(const BinaryOperator *E) { llvm_unreachable("NYI"); } + void VisitCXXRewrittenBinaryOperator(CXXRewrittenBinaryOperator *E) { + llvm_unreachable("NYI"); + } + + void VisitObjCMessageExpr(ObjCMessageExpr *E) { llvm_unreachable("NYI"); } + void VisitObjCIVarRefExpr(ObjCIvarRefExpr *E) { llvm_unreachable("NYI"); } + + void VisitDesignatedInitUpdateExpr(DesignatedInitUpdateExpr *E) { + llvm_unreachable("NYI"); + } + void VisitAbstractConditionalOperator(const AbstractConditionalOperator *E) { + llvm_unreachable("NYI"); + } + void VisitChooseExpr(const ChooseExpr *E) { llvm_unreachable("NYI"); } + void VisitInitListExpr(InitListExpr *E); + void VisitCXXParenListOrInitListExpr(Expr *ExprToVisit, ArrayRef Args, + FieldDecl *InitializedFieldInUnion, + Expr *ArrayFiller); + void VisitArrayInitLoopExpr(const ArrayInitLoopExpr *E, + llvm::Value *outerBegin = nullptr) { + llvm_unreachable("NYI"); + } + void VisitImplicitValueInitExpr(ImplicitValueInitExpr *E) { + llvm_unreachable("NYI"); + } + void VisitNoInitExpr(NoInitExpr *E) { llvm_unreachable("NYI"); } + void VisitCXXDefaultArgExpr(CXXDefaultArgExpr *DAE) { + CIRGenFunction::CXXDefaultArgExprScope Scope(CGF, DAE); + Visit(DAE->getExpr()); + } + void VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DIE) { + CIRGenFunction::CXXDefaultInitExprScope Scope(CGF, DIE); + Visit(DIE->getExpr()); + } + void VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E); + void VisitCXXConstructExpr(const CXXConstructExpr *E); + void VisitCXXInheritedCtorInitExpr(const CXXInheritedCtorInitExpr *E) { + llvm_unreachable("NYI"); + } + void VisitLambdaExpr(LambdaExpr *E); + void VisitCXXStdInitializerListExpr(CXXStdInitializerListExpr *E) { + llvm_unreachable("NYI"); + } + void VisitExprWithCleanups(ExprWithCleanups *E); + void VisitCXXScalarValueInitExpr(CXXScalarValueInitExpr *E) { + llvm_unreachable("NYI"); + } + void VisitCXXTypeidExpr(CXXTypeidExpr *E) { llvm_unreachable("NYI"); } + void VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E); + void VisitOpaqueValueExpr(OpaqueValueExpr *E) { llvm_unreachable("NYI"); } + + void VisitPseudoObjectExpr(PseudoObjectExpr *E) { llvm_unreachable("NYI"); } + + void VisitVAArgExpr(VAArgExpr *E) { llvm_unreachable("NYI"); } + + void buildInitializationToLValue(Expr *E, LValue LV); + + void buildNullInitializationToLValue(mlir::Location loc, LValue Address); + void VisitCXXThrowExpr(const CXXThrowExpr *E) { llvm_unreachable("NYI"); } + void VisitAtomicExpr(AtomicExpr *E) { llvm_unreachable("NYI"); } +}; +} // namespace + +//===----------------------------------------------------------------------===// +// Utilities +//===----------------------------------------------------------------------===// + +/// Given an expression with aggregate type that represents a value lvalue, this +/// method emits the address of the lvalue, then loads the result into DestPtr. +void AggExprEmitter::buildAggLoadOfLValue(const Expr *E) { + LValue LV = CGF.buildLValue(E); + + // If the type of the l-value is atomic, then do an atomic load. + if (LV.getType()->isAtomicType() || CGF.LValueIsSuitableForInlineAtomic(LV) || + UnimplementedFeature::atomicTypes()) + llvm_unreachable("atomic load is NYI"); + + buildFinalDestCopy(E->getType(), LV); +} + +/// Perform the final copy to DestPtr, if desired. +void AggExprEmitter::buildFinalDestCopy(QualType type, const LValue &src, + ExprValueKind SrcValueKind) { + // If Dest is ignored, then we're evaluating an aggregate expression + // in a context that doesn't care about the result. Note that loads + // from volatile l-values force the existence of a non-ignored + // destination. + if (Dest.isIgnored()) + return; + + // Copy non-trivial C structs here. + if (Dest.isVolatile() || UnimplementedFeature::volatileTypes()) + llvm_unreachable("volatile is NYI"); + + if (SrcValueKind == EVK_RValue) { + llvm_unreachable("rvalue is NYI"); + } else { + if (type.isNonTrivialToPrimitiveCopy() == QualType::PCK_Struct) + llvm_unreachable("non-trivial primitive copy is NYI"); + } + + AggValueSlot srcAgg = AggValueSlot::forLValue( + src, AggValueSlot::IsDestructed, needsGC(type), AggValueSlot::IsAliased, + AggValueSlot::MayOverlap); + buildCopy(type, Dest, srcAgg); +} + +/// Perform a copy from the source into the destination. +/// +/// \param type - the type of the aggregate being copied; qualifiers are +/// ignored +void AggExprEmitter::buildCopy(QualType type, const AggValueSlot &dest, + const AggValueSlot &src) { + if (dest.requiresGCollection()) + llvm_unreachable("garbage collection is NYI"); + + // If the result of the assignment is used, copy the LHS there also. + // It's volatile if either side is. Use the minimum alignment of + // the two sides. + LValue DestLV = CGF.makeAddrLValue(dest.getAddress(), type); + LValue SrcLV = CGF.makeAddrLValue(src.getAddress(), type); + if (dest.isVolatile() || src.isVolatile() || + UnimplementedFeature::volatileTypes()) + llvm_unreachable("volatile is NYI"); + CGF.buildAggregateCopy(DestLV, SrcLV, type, dest.mayOverlap(), false); +} + +/// True if the given aggregate type requires special GC API calls. +bool AggExprEmitter::TypeRequiresGCollection(QualType T) { + // Only record types have members that might require garbage collection. + const RecordType *RecordTy = T->getAs(); + if (!RecordTy) + return false; + + // Don't mess with non-trivial C++ types. + RecordDecl *Record = RecordTy->getDecl(); + if (isa(Record) && + (cast(Record)->hasNonTrivialCopyConstructor() || + !cast(Record)->hasTrivialDestructor())) + return false; + + // Check whether the type has an object member. + return Record->hasObjectMember(); +} + +//===----------------------------------------------------------------------===// +// Visitor Methods +//===----------------------------------------------------------------------===// + +/// Determine whether the given cast kind is known to always convert values +/// with all zero bits in their value representation to values with all zero +/// bits in their value representation. +/// TODO(cir): this can be shared with LLVM codegen. +static bool castPreservesZero(const CastExpr *CE) { + switch (CE->getCastKind()) { + // No-ops. + case CK_NoOp: + case CK_UserDefinedConversion: + case CK_ConstructorConversion: + case CK_BitCast: + case CK_ToUnion: + case CK_ToVoid: + // Conversions between (possibly-complex) integral, (possibly-complex) + // floating-point, and bool. + case CK_BooleanToSignedIntegral: + case CK_FloatingCast: + case CK_FloatingComplexCast: + case CK_FloatingComplexToBoolean: + case CK_FloatingComplexToIntegralComplex: + case CK_FloatingComplexToReal: + case CK_FloatingRealToComplex: + case CK_FloatingToBoolean: + case CK_FloatingToIntegral: + case CK_IntegralCast: + case CK_IntegralComplexCast: + case CK_IntegralComplexToBoolean: + case CK_IntegralComplexToFloatingComplex: + case CK_IntegralComplexToReal: + case CK_IntegralRealToComplex: + case CK_IntegralToBoolean: + case CK_IntegralToFloating: + // Reinterpreting integers as pointers and vice versa. + case CK_IntegralToPointer: + case CK_PointerToIntegral: + // Language extensions. + case CK_VectorSplat: + case CK_MatrixCast: + case CK_NonAtomicToAtomic: + case CK_AtomicToNonAtomic: + return true; + + case CK_BaseToDerivedMemberPointer: + case CK_DerivedToBaseMemberPointer: + case CK_MemberPointerToBoolean: + case CK_NullToMemberPointer: + case CK_ReinterpretMemberPointer: + // FIXME: ABI-dependent. + return false; + + case CK_AnyPointerToBlockPointerCast: + case CK_BlockPointerToObjCPointerCast: + case CK_CPointerToObjCPointerCast: + case CK_ObjCObjectLValueCast: + case CK_IntToOCLSampler: + case CK_ZeroToOCLOpaqueType: + // FIXME: Check these. + return false; + + case CK_FixedPointCast: + case CK_FixedPointToBoolean: + case CK_FixedPointToFloating: + case CK_FixedPointToIntegral: + case CK_FloatingToFixedPoint: + case CK_IntegralToFixedPoint: + // FIXME: Do all fixed-point types represent zero as all 0 bits? + return false; + + case CK_AddressSpaceConversion: + case CK_BaseToDerived: + case CK_DerivedToBase: + case CK_Dynamic: + case CK_NullToPointer: + case CK_PointerToBoolean: + // FIXME: Preserves zeroes only if zero pointers and null pointers have the + // same representation in all involved address spaces. + return false; + + case CK_ARCConsumeObject: + case CK_ARCExtendBlockObject: + case CK_ARCProduceObject: + case CK_ARCReclaimReturnedObject: + case CK_CopyAndAutoreleaseBlockObject: + case CK_ArrayToPointerDecay: + case CK_FunctionToPointerDecay: + case CK_BuiltinFnToFnPtr: + case CK_Dependent: + case CK_LValueBitCast: + case CK_LValueToRValue: + case CK_LValueToRValueBitCast: + case CK_UncheckedDerivedToBase: + return false; + } + llvm_unreachable("Unhandled clang::CastKind enum"); +} + +/// If emitting this value will obviously just cause a store of +/// zero to memory, return true. This can return false if uncertain, so it just +/// handles simple cases. +static bool isSimpleZero(const Expr *E, CIRGenFunction &CGF) { + E = E->IgnoreParens(); + while (auto *CE = dyn_cast(E)) { + if (!castPreservesZero(CE)) + break; + E = CE->getSubExpr()->IgnoreParens(); + } + + // 0 + if (const IntegerLiteral *IL = dyn_cast(E)) + return IL->getValue() == 0; + // +0.0 + if (const FloatingLiteral *FL = dyn_cast(E)) + return FL->getValue().isPosZero(); + // int() + if ((isa(E) || isa(E)) && + CGF.getTypes().isZeroInitializable(E->getType())) + return true; + // (int*)0 - Null pointer expressions. + if (const CastExpr *ICE = dyn_cast(E)) { + llvm_unreachable("NYI"); + // return ICE->getCastKind() == CK_NullToPointer && + // CGF.getTypes().isPointerZeroInitializable(E->getType()) && + // !E->HasSideEffects(CGF.getContext()); + } + // '\0' + if (const CharacterLiteral *CL = dyn_cast(E)) + return CL->getValue() == 0; + + // Otherwise, hard case: conservatively return false. + return false; +} + +void AggExprEmitter::buildNullInitializationToLValue(mlir::Location loc, + LValue lv) { + QualType type = lv.getType(); + + // If the destination slot is already zeroed out before the aggregate is + // copied into it, we don't have to emit any zeros here. + if (Dest.isZeroed() && CGF.getTypes().isZeroInitializable(type)) + return; + + if (CGF.hasScalarEvaluationKind(type)) { + // For non-aggregates, we can store the appropriate null constant. + auto null = CGF.CGM.buildNullConstant(type, loc); + // Note that the following is not equivalent to + // EmitStoreThroughBitfieldLValue for ARC types. + if (lv.isBitField()) { + llvm_unreachable("NYI"); + } else { + assert(lv.isSimple()); + CGF.buildStoreOfScalar(null, lv, /* isInitialization */ true); + } + } else { + // There's a potential optimization opportunity in combining + // memsets; that would be easy for arrays, but relatively + // difficult for structures with the current code. + CGF.buildNullInitialization(loc, lv.getAddress(), lv.getType()); + } +} + +void AggExprEmitter::buildInitializationToLValue(Expr *E, LValue LV) { + QualType type = LV.getType(); + // FIXME: Ignore result? + // FIXME: Are initializers affected by volatile? + if (Dest.isZeroed() && isSimpleZero(E, CGF)) { + // TODO(cir): LLVM codegen considers 'storing "i32 0" to a zero'd memory + // location is a noop'. Consider emitting the store to zero in CIR, as to + // model the actual user behavior, we can have a pass to optimize this out + // later. + return; + } + + if (isa(E) || isa(E)) { + auto loc = E->getSourceRange().isValid() ? CGF.getLoc(E->getSourceRange()) + : *CGF.currSrcLoc; + return buildNullInitializationToLValue(loc, LV); + } else if (isa(E)) { + // Do nothing. + return; + } else if (type->isReferenceType()) { + RValue RV = CGF.buildReferenceBindingToExpr(E); + return CGF.buildStoreThroughLValue(RV, LV); + } + + switch (CGF.getEvaluationKind(type)) { + case TEK_Complex: + llvm_unreachable("NYI"); + return; + case TEK_Aggregate: + CGF.buildAggExpr( + E, AggValueSlot::forLValue(LV, AggValueSlot::IsDestructed, + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased, + AggValueSlot::MayOverlap, Dest.isZeroed())); + return; + case TEK_Scalar: + if (LV.isSimple()) { + CGF.buildScalarInit(E, CGF.getLoc(E->getSourceRange()), LV); + } else { + llvm_unreachable("NYI"); + // CGF.EmitStoreThroughLValue(RValue::get(CGF.EmitScalarExpr(E)), LV); + } + return; + } + llvm_unreachable("bad evaluation kind"); +} + +void AggExprEmitter::VisitMaterializeTemporaryExpr( + MaterializeTemporaryExpr *E) { + Visit(E->getSubExpr()); +} + +void AggExprEmitter::VisitCXXConstructExpr(const CXXConstructExpr *E) { + AggValueSlot Slot = EnsureSlot(E->getType()); + CGF.buildCXXConstructExpr(E, Slot); +} + +void AggExprEmitter::VisitExprWithCleanups(ExprWithCleanups *E) { + if (UnimplementedFeature::cleanups()) + llvm_unreachable("NYI"); + + auto &builder = CGF.getBuilder(); + auto scopeLoc = CGF.getLoc(E->getSourceRange()); + [[maybe_unused]] auto scope = builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + loc, builder.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexScopeGuard{CGF, &lexScope}; + Visit(E->getSubExpr()); + }); +} + +void AggExprEmitter::VisitLambdaExpr(LambdaExpr *E) { + CIRGenFunction::SourceLocRAIIObject loc{CGF, CGF.getLoc(E->getSourceRange())}; + AggValueSlot Slot = EnsureSlot(E->getType()); + LLVM_ATTRIBUTE_UNUSED LValue SlotLV = + CGF.makeAddrLValue(Slot.getAddress(), E->getType()); + + // We'll need to enter cleanup scopes in case any of the element initializers + // throws an exception. + if (UnimplementedFeature::cleanups()) + llvm_unreachable("NYI"); + mlir::Operation *CleanupDominator = nullptr; + + auto CurField = E->getLambdaClass()->field_begin(); + auto captureInfo = E->capture_begin(); + for (auto &captureInit : E->capture_inits()) { + // Pick a name for the field. + llvm::StringRef fieldName = CurField->getName(); + const LambdaCapture &capture = *captureInfo; + if (capture.capturesVariable()) { + assert(!CurField->isBitField() && "lambdas don't have bitfield members!"); + ValueDecl *v = capture.getCapturedVar(); + fieldName = v->getName(); + CGF.getCIRGenModule().LambdaFieldToName[*CurField] = fieldName; + } else { + llvm_unreachable("NYI"); + } + + // Emit initialization + LValue LV = CGF.buildLValueForFieldInitialization( + SlotLV, *CurField, fieldName); + if (CurField->hasCapturedVLAType()) { + llvm_unreachable("NYI"); + } + + buildInitializationToLValue(captureInit, LV); + + // Push a destructor if necessary. + if (QualType::DestructionKind DtorKind = + CurField->getType().isDestructedType()) { + llvm_unreachable("NYI"); + } + + CurField++; + captureInfo++; + } + + // Deactivate all the partial cleanups in reverse order, which generally means + // popping them. + if (UnimplementedFeature::cleanups()) + llvm_unreachable("NYI"); + + // Destroy the placeholder if we made one. + if (CleanupDominator) + CleanupDominator->erase(); +} + +void AggExprEmitter::VisitCastExpr(CastExpr *E) { + if (const auto *ECE = dyn_cast(E)) + CGF.CGM.buildExplicitCastExprType(ECE, &CGF); + switch (E->getCastKind()) { + + case CK_LValueToRValue: + // If we're loading from a volatile type, force the destination + // into existence. + if (E->getSubExpr()->getType().isVolatileQualified() || + UnimplementedFeature::volatileTypes()) { + llvm_unreachable("volatile is NYI"); + } + [[fallthrough]]; + + case CK_NoOp: + case CK_UserDefinedConversion: + case CK_ConstructorConversion: + assert(CGF.getContext().hasSameUnqualifiedType(E->getSubExpr()->getType(), + E->getType()) && + "Implicit cast types must be compatible"); + Visit(E->getSubExpr()); + break; + + case CK_LValueBitCast: + llvm_unreachable("should not be emitting lvalue bitcast as rvalue"); + + case CK_Dependent: + case CK_BitCast: + case CK_ArrayToPointerDecay: + case CK_FunctionToPointerDecay: + case CK_NullToPointer: + case CK_NullToMemberPointer: + case CK_BaseToDerivedMemberPointer: + case CK_DerivedToBaseMemberPointer: + case CK_MemberPointerToBoolean: + case CK_ReinterpretMemberPointer: + case CK_IntegralToPointer: + case CK_PointerToIntegral: + case CK_PointerToBoolean: + case CK_ToVoid: + case CK_VectorSplat: + case CK_IntegralCast: + case CK_BooleanToSignedIntegral: + case CK_IntegralToBoolean: + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + case CK_FloatingToBoolean: + case CK_FloatingCast: + case CK_CPointerToObjCPointerCast: + case CK_BlockPointerToObjCPointerCast: + case CK_AnyPointerToBlockPointerCast: + case CK_ObjCObjectLValueCast: + case CK_FloatingRealToComplex: + case CK_FloatingComplexToReal: + case CK_FloatingComplexToBoolean: + case CK_FloatingComplexCast: + case CK_FloatingComplexToIntegralComplex: + case CK_IntegralRealToComplex: + case CK_IntegralComplexToReal: + case CK_IntegralComplexToBoolean: + case CK_IntegralComplexCast: + case CK_IntegralComplexToFloatingComplex: + case CK_ARCProduceObject: + case CK_ARCConsumeObject: + case CK_ARCReclaimReturnedObject: + case CK_ARCExtendBlockObject: + case CK_CopyAndAutoreleaseBlockObject: + case CK_BuiltinFnToFnPtr: + case CK_ZeroToOCLOpaqueType: + case CK_MatrixCast: + + case CK_IntToOCLSampler: + case CK_FloatingToFixedPoint: + case CK_FixedPointToFloating: + case CK_FixedPointCast: + case CK_FixedPointToBoolean: + case CK_FixedPointToIntegral: + case CK_IntegralToFixedPoint: + llvm::errs() << "cast '" << E->getCastKindName() + << "' invalid for aggregate types\n"; + llvm_unreachable("cast kind invalid for aggregate types"); + default: { + llvm::errs() << "cast kind not implemented: '" << E->getCastKindName() + << "'\n"; + assert(0 && "not implemented"); + break; + } + } +} + +void AggExprEmitter::VisitCallExpr(const CallExpr *E) { + if (E->getCallReturnType(CGF.getContext())->isReferenceType()) { + llvm_unreachable("NYI"); + } + + withReturnValueSlot( + E, [&](ReturnValueSlot Slot) { return CGF.buildCallExpr(E, Slot); }); +} + +void AggExprEmitter::withReturnValueSlot( + const Expr *E, llvm::function_ref EmitCall) { + QualType RetTy = E->getType(); + bool RequiresDestruction = + !Dest.isExternallyDestructed() && + RetTy.isDestructedType() == QualType::DK_nontrivial_c_struct; + + // If it makes no observable difference, save a memcpy + temporary. + // + // We need to always provide our own temporary if destruction is required. + // Otherwise, EmitCall will emit its own, notice that it's "unused", and end + // its lifetime before we have the chance to emit a proper destructor call. + bool UseTemp = Dest.isPotentiallyAliased() || Dest.requiresGCollection() || + (RequiresDestruction && !Dest.getAddress().isValid()); + + Address RetAddr = Address::invalid(); + assert(!UnimplementedFeature::shouldEmitLifetimeMarkers() && "NYI"); + + if (!UseTemp) { + RetAddr = Dest.getAddress(); + } else { + llvm_unreachable("NYI"); + } + + RValue Src = + EmitCall(ReturnValueSlot(RetAddr, Dest.isVolatile(), IsResultUnused, + Dest.isExternallyDestructed())); + + if (!UseTemp) + return; + + assert(Dest.isIgnored() || Dest.getPointer() != Src.getAggregatePointer()); + llvm_unreachable("NYI"); + // TODO(cir): EmitFinalDestCopy(E->getType(), Src); + + if (!RequiresDestruction) { + // If there's no dtor to run, the copy was the last use of our temporary. + // Since we're not guaranteed to be in an ExprWithCleanups, clean up + // eagerly. + llvm_unreachable("NYI"); + } +} + +void AggExprEmitter::VisitInitListExpr(InitListExpr *E) { + // TODO(cir): use something like CGF.ErrorUnsupported + if (E->hadArrayRangeDesignator()) + llvm_unreachable("GNU array range designator extension"); + + if (E->isTransparent()) + return Visit(E->getInit(0)); + + VisitCXXParenListOrInitListExpr( + E, E->inits(), E->getInitializedFieldInUnion(), E->getArrayFiller()); +} + +void AggExprEmitter::VisitCXXParenListOrInitListExpr( + Expr *ExprToVisit, ArrayRef InitExprs, + FieldDecl *InitializedFieldInUnion, Expr *ArrayFiller) { +#if 0 + // FIXME: Assess perf here? Figure out what cases are worth optimizing here + // (Length of globals? Chunks of zeroed-out space?). + // + // If we can, prefer a copy from a global; this is a lot less code for long + // globals, and it's easier for the current optimizers to analyze. + if (llvm::Constant *C = + CGF.CGM.EmitConstantExpr(ExprToVisit, ExprToVisit->getType(), &CGF)) { + llvm::GlobalVariable* GV = + new llvm::GlobalVariable(CGF.CGM.getModule(), C->getType(), true, + llvm::GlobalValue::InternalLinkage, C, ""); + EmitFinalDestCopy(ExprToVisit->getType(), + CGF.MakeAddrLValue(GV, ExprToVisit->getType())); + return; + } +#endif + + AggValueSlot Dest = EnsureSlot(ExprToVisit->getType()); + + LValue DestLV = CGF.makeAddrLValue(Dest.getAddress(), ExprToVisit->getType()); + + // Handle initialization of an array. + if (ExprToVisit->getType()->isArrayType()) { + llvm_unreachable("NYI"); + } + + assert(ExprToVisit->getType()->isRecordType() && + "Only support structs/unions here!"); + + // Do struct initialization; this code just sets each individual member + // to the approprate value. This makes bitfield support automatic; + // the disadvantage is that the generated code is more difficult for + // the optimizer, especially with bitfields. + unsigned NumInitElements = InitExprs.size(); + RecordDecl *record = ExprToVisit->getType()->castAs()->getDecl(); + + // We'll need to enter cleanup scopes in case any of the element + // initializers throws an exception. + SmallVector cleanups; + // FIXME(cir): placeholder + mlir::Operation *cleanupDominator = nullptr; + [[maybe_unused]] auto addCleanup = + [&](const EHScopeStack::stable_iterator &cleanup) { + llvm_unreachable("NYI"); + }; + + unsigned curInitIndex = 0; + + // Emit initialization of base classes. + if (auto *CXXRD = dyn_cast(record)) { + assert(NumInitElements >= CXXRD->getNumBases() && + "missing initializer for base class"); + for ([[maybe_unused]] auto &Base : CXXRD->bases()) { + llvm_unreachable("NYI"); + } + } + + // Prepare a 'this' for CXXDefaultInitExprs. + CIRGenFunction::FieldConstructionScope FCS(CGF, Dest.getAddress()); + + if (record->isUnion()) { + llvm_unreachable("NYI"); + } + + // Here we iterate over the fields; this makes it simpler to both + // default-initialize fields and skip over unnamed fields. + for (const auto *field : record->fields()) { + // We're done once we hit the flexible array member. + if (field->getType()->isIncompleteArrayType()) + break; + + // Always skip anonymous bitfields. + if (field->isUnnamedBitfield()) + continue; + + // We're done if we reach the end of the explicit initializers, we + // have a zeroed object, and the rest of the fields are + // zero-initializable. + if (curInitIndex == NumInitElements && Dest.isZeroed() && + CGF.getTypes().isZeroInitializable(ExprToVisit->getType())) + break; + LValue LV = CGF.buildLValueForFieldInitialization( + DestLV, field, field->getName()); + // We never generate write-barries for initialized fields. + assert(!UnimplementedFeature::setNonGC()); + + if (curInitIndex < NumInitElements) { + // Store the initializer into the field. + CIRGenFunction::SourceLocRAIIObject loc{ + CGF, CGF.getLoc(record->getSourceRange())}; + buildInitializationToLValue(InitExprs[curInitIndex++], LV); + } else { + // We're out of initializers; default-initialize to null + buildNullInitializationToLValue(CGF.getLoc(ExprToVisit->getSourceRange()), + LV); + } + + // Push a destructor if necessary. + // FIXME: if we have an array of structures, all explicitly + // initialized, we can end up pushing a linear number of cleanups. + [[maybe_unused]] bool pushedCleanup = false; + if (QualType::DestructionKind dtorKind = + field->getType().isDestructedType()) { + llvm_unreachable("NYI"); + } + + // From LLVM codegen, maybe not useful for CIR: + // If the GEP didn't get used because of a dead zero init or something + // else, clean it up for -O0 builds and general tidiness. + } + + // Deactivate all the partial cleanups in reverse order, which + // generally means popping them. + assert((cleanupDominator || cleanups.empty()) && + "Missing cleanupDominator before deactivating cleanup blocks"); + for (unsigned i = cleanups.size(); i != 0; --i) + llvm_unreachable("NYI"); + + // Destroy the placeholder if we made one. + if (cleanupDominator) + llvm_unreachable("NYI"); +} + +void AggExprEmitter::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *E) { + // Ensure that we have a slot, but if we already do, remember + // whether it was externally destructed. + bool wasExternallyDestructed = Dest.isExternallyDestructed(); + EnsureDest(CGF.getLoc(E->getSourceRange()), E->getType()); + + // We're going to push a destructor if there isn't already one. + Dest.setExternallyDestructed(); + + Visit(E->getSubExpr()); + + // Push that destructor we promised. + if (!wasExternallyDestructed) + CGF.buildCXXTemporary(E->getTemporary(), E->getType(), Dest.getAddress()); +} + +//===----------------------------------------------------------------------===// +// Helpers and dispatcher +//===----------------------------------------------------------------------===// + +/// Get an approximate count of the number of non-zero bytes that will be stored +/// when outputting the initializer for the specified initializer expression. +/// FIXME(cir): this can be shared with LLVM codegen. +static CharUnits GetNumNonZeroBytesInInit(const Expr *E, CIRGenFunction &CGF) { + if (auto *MTE = dyn_cast(E)) + E = MTE->getSubExpr(); + E = E->IgnoreParenNoopCasts(CGF.getContext()); + + // 0 and 0.0 won't require any non-zero stores! + if (isSimpleZero(E, CGF)) + return CharUnits::Zero(); + + // If this is an initlist expr, sum up the size of sizes of the (present) + // elements. If this is something weird, assume the whole thing is non-zero. + const InitListExpr *ILE = dyn_cast(E); + while (ILE && ILE->isTransparent()) + ILE = dyn_cast(ILE->getInit(0)); + if (!ILE || !CGF.getTypes().isZeroInitializable(ILE->getType())) + return CGF.getContext().getTypeSizeInChars(E->getType()); + + // InitListExprs for structs have to be handled carefully. If there are + // reference members, we need to consider the size of the reference, not the + // referencee. InitListExprs for unions and arrays can't have references. + if (const RecordType *RT = E->getType()->getAs()) { + if (!RT->isUnionType()) { + RecordDecl *SD = RT->getDecl(); + CharUnits NumNonZeroBytes = CharUnits::Zero(); + + unsigned ILEElement = 0; + if (auto *CXXRD = dyn_cast(SD)) + while (ILEElement != CXXRD->getNumBases()) + NumNonZeroBytes += + GetNumNonZeroBytesInInit(ILE->getInit(ILEElement++), CGF); + for (const auto *Field : SD->fields()) { + // We're done once we hit the flexible array member or run out of + // InitListExpr elements. + if (Field->getType()->isIncompleteArrayType() || + ILEElement == ILE->getNumInits()) + break; + if (Field->isUnnamedBitfield()) + continue; + + const Expr *E = ILE->getInit(ILEElement++); + + // Reference values are always non-null and have the width of a pointer. + if (Field->getType()->isReferenceType()) + NumNonZeroBytes += CGF.getContext().toCharUnitsFromBits( + CGF.getTarget().getPointerWidth(LangAS::Default)); + else + NumNonZeroBytes += GetNumNonZeroBytesInInit(E, CGF); + } + + return NumNonZeroBytes; + } + } + + // FIXME: This overestimates the number of non-zero bytes for bit-fields. + CharUnits NumNonZeroBytes = CharUnits::Zero(); + for (unsigned i = 0, e = ILE->getNumInits(); i != e; ++i) + NumNonZeroBytes += GetNumNonZeroBytesInInit(ILE->getInit(i), CGF); + return NumNonZeroBytes; +} + +/// If the initializer is large and has a lot of zeros in it, emit a memset and +/// avoid storing the individual zeros. +static void CheckAggExprForMemSetUse(AggValueSlot &Slot, const Expr *E, + CIRGenFunction &CGF) { + // If the slot is arleady known to be zeroed, nothing to do. Don't mess with + // volatile stores. + if (Slot.isZeroed() || Slot.isVolatile() || !Slot.getAddress().isValid()) + return; + + // C++ objects with a user-declared constructor don't need zero'ing. + if (CGF.getLangOpts().CPlusPlus) + if (const auto *RT = CGF.getContext() + .getBaseElementType(E->getType()) + ->getAs()) { + const auto *RD = cast(RT->getDecl()); + if (RD->hasUserDeclaredConstructor()) + return; + } + + // If the type is 16-bytes or smaller, prefer individual stores over memset. + CharUnits Size = Slot.getPreferredSize(CGF.getContext(), E->getType()); + if (Size <= CharUnits::fromQuantity(16)) + return; + + // Check to see if over 3/4 of the initializer are known to be zero. If so, + // we prefer to emit memset + individual stores for the rest. + CharUnits NumNonZeroBytes = GetNumNonZeroBytesInInit(E, CGF); + if (NumNonZeroBytes * 4 > Size) + return; + + // Okay, it seems like a good idea to use an initial memset, emit the call. + auto &builder = CGF.getBuilder(); + auto loc = CGF.getLoc(E->getSourceRange()); + Address slotAddr = Slot.getAddress(); + auto zero = builder.getZero(loc, slotAddr.getElementType()); + + builder.createStore(loc, zero, slotAddr); + // Loc = CGF.Builder.CreateElementBitCast(Loc, CGF.Int8Ty); + // CGF.Builder.CreateMemSet(Loc, CGF.Builder.getInt8(0), SizeVal, false); + + // Tell the AggExprEmitter that the slot is known zero. + Slot.setZeroed(); +} + +AggValueSlot::Overlap_t CIRGenFunction::getOverlapForBaseInit( + const CXXRecordDecl *RD, const CXXRecordDecl *BaseRD, bool IsVirtual) { + // If the most-derived object is a field declared with [[no_unique_address]], + // the tail padding of any virtual base could be reused for other subobjects + // of that field's class. + if (IsVirtual) + return AggValueSlot::MayOverlap; + + // If the base class is laid out entirely within the nvsize of the derived + // class, its tail padding cannot yet be initialized, so we can issue + // stores at the full width of the base class. + const ASTRecordLayout &Layout = getContext().getASTRecordLayout(RD); + if (Layout.getBaseClassOffset(BaseRD) + + getContext().getASTRecordLayout(BaseRD).getSize() <= + Layout.getNonVirtualSize()) + return AggValueSlot::DoesNotOverlap; + + // The tail padding may contain values we need to preserve. + return AggValueSlot::MayOverlap; +} + +void CIRGenFunction::buildAggExpr(const Expr *E, AggValueSlot Slot) { + assert(E && CIRGenFunction::hasAggregateEvaluationKind(E->getType()) && + "Invalid aggregate expression to emit"); + assert((Slot.getAddress().isValid() || Slot.isIgnored()) && + "slot has bits but no address"); + + // Optimize the slot if possible. + CheckAggExprForMemSetUse(Slot, E, *this); + + AggExprEmitter(*this, Slot, Slot.isIgnored()).Visit(const_cast(E)); +} + +void CIRGenFunction::buildAggregateCopy(LValue Dest, LValue Src, QualType Ty, + AggValueSlot::Overlap_t MayOverlap, + bool isVolatile) { + // TODO(cir): this function needs improvements, commented code for now since + // this will be touched again soon. + assert(!Ty->isAnyComplexType() && "Shouldn't happen for complex"); + + Address DestPtr = Dest.getAddress(); + Address SrcPtr = Src.getAddress(); + + if (getLangOpts().CPlusPlus) { + if (const RecordType *RT = Ty->getAs()) { + CXXRecordDecl *Record = cast(RT->getDecl()); + assert((Record->hasTrivialCopyConstructor() || + Record->hasTrivialCopyAssignment() || + Record->hasTrivialMoveConstructor() || + Record->hasTrivialMoveAssignment() || + Record->hasAttr() || Record->isUnion()) && + "Trying to aggregate-copy a type without a trivial copy/move " + "constructor or assignment operator"); + // Ignore empty classes in C++. + if (Record->isEmpty()) + return; + } + } + + if (getLangOpts().CUDAIsDevice) { + llvm_unreachable("CUDA is NYI"); + } + + // Aggregate assignment turns into llvm.memcpy. This is almost valid per + // C99 6.5.16.1p3, which states "If the value being stored in an object is + // read from another object that overlaps in anyway the storage of the first + // object, then the overlap shall be exact and the two objects shall have + // qualified or unqualified versions of a compatible type." + // + // memcpy is not defined if the source and destination pointers are exactly + // equal, but other compilers do this optimization, and almost every memcpy + // implementation handles this case safely. If there is a libc that does not + // safely handle this, we can add a target hook. + + // Get data size info for this aggregate. Don't copy the tail padding if this + // might be a potentially-overlapping subobject, since the tail padding might + // be occupied by a different object. Otherwise, copying it is fine. + TypeInfoChars TypeInfo; + if (MayOverlap) + TypeInfo = getContext().getTypeInfoDataSizeInChars(Ty); + else + TypeInfo = getContext().getTypeInfoInChars(Ty); + + mlir::Attribute SizeVal = nullptr; + if (TypeInfo.Width.isZero()) { + // But note that getTypeInfo returns 0 for a VLA. + if (auto *VAT = dyn_cast_or_null( + getContext().getAsArrayType(Ty))) { + llvm_unreachable("VLA is NYI"); + } + } + if (!SizeVal) { + // NOTE(cir): CIR types already carry info about their sizes. This is here + // just for codegen parity. + SizeVal = builder.getI64IntegerAttr(TypeInfo.Width.getQuantity()); + } + + // FIXME: If we have a volatile struct, the optimizer can remove what might + // appear to be `extra' memory ops: + // + // volatile struct { int i; } a, b; + // + // int main() { + // a = b; + // a = b; + // } + // + // we need to use a different call here. We use isVolatile to indicate when + // either the source or the destination is volatile. + + // NOTE(cir): original codegen would normally convert DestPtr and SrcPtr to + // i8* since memcpy operates on bytes. We don't need that in CIR because + // cir.copy will operate on any CIR pointer that points to a sized type. + + // Don't do any of the memmove_collectable tests if GC isn't set. + if (CGM.getLangOpts().getGC() == LangOptions::NonGC) { + // fall through + } else if (const RecordType *RecordTy = Ty->getAs()) { + RecordDecl *Record = RecordTy->getDecl(); + if (Record->hasObjectMember()) { + llvm_unreachable("ObjC is NYI"); + } + } else if (Ty->isArrayType()) { + QualType BaseType = getContext().getBaseElementType(Ty); + if (const RecordType *RecordTy = BaseType->getAs()) { + if (RecordTy->getDecl()->hasObjectMember()) { + llvm_unreachable("ObjC is NYI"); + } + } + } + + builder.createCopy(DestPtr.getPointer(), SrcPtr.getPointer()); + + // Determine the metadata to describe the position of any padding in this + // memcpy, as well as the TBAA tags for the members of the struct, in case + // the optimizer wishes to expand it in to scalar memory operations. + if (CGM.getCodeGenOpts().NewStructPathTBAA || UnimplementedFeature::tbaa()) + llvm_unreachable("TBAA is NYI"); +} + +AggValueSlot::Overlap_t +CIRGenFunction::getOverlapForFieldInit(const FieldDecl *FD) { + if (!FD->hasAttr() || !FD->getType()->isRecordType()) + return AggValueSlot::DoesNotOverlap; + + // If the field lies entirely within the enclosing class's nvsize, its tail + // padding cannot overlap any already-initialized object. (The only subobjects + // with greater addresses that might already be initialized are vbases.) + const RecordDecl *ClassRD = FD->getParent(); + const ASTRecordLayout &Layout = getContext().getASTRecordLayout(ClassRD); + if (Layout.getFieldOffset(FD->getFieldIndex()) + + getContext().getTypeSize(FD->getType()) <= + (uint64_t)getContext().toBits(Layout.getNonVirtualSize())) + return AggValueSlot::DoesNotOverlap; + + // The tail padding may contain values we need to preserve. + return AggValueSlot::MayOverlap; +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp new file mode 100644 index 000000000000..b1cb9b137a26 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp @@ -0,0 +1,913 @@ +//===--- CIRGenExprCXX.cpp - Emit CIR Code for C++ expressions ------------===// +// +// 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 dealing with code generation of C++ expressions +// +//===----------------------------------------------------------------------===// + +#include +#include +#include +#include +#include + +#include + +using namespace cir; +using namespace clang; + +namespace { +struct MemberCallInfo { + RequiredArgs ReqArgs; + // Number of prefix arguments for the call. Ignores the `this` pointer. + unsigned PrefixSize; +}; +} // namespace + +static RValue buildNewDeleteCall(CIRGenFunction &CGF, + const FunctionDecl *CalleeDecl, + const FunctionProtoType *CalleeType, + const CallArgList &Args); + +static MemberCallInfo +commonBuildCXXMemberOrOperatorCall(CIRGenFunction &CGF, const CXXMethodDecl *MD, + mlir::Value This, mlir::Value ImplicitParam, + QualType ImplicitParamTy, const CallExpr *CE, + CallArgList &Args, CallArgList *RtlArgs) { + assert(CE == nullptr || isa(CE) || + isa(CE)); + assert(MD->isInstance() && + "Trying to emit a member or operator call expr on a static method!"); + + // Push the this ptr. + const CXXRecordDecl *RD = + CGF.CGM.getCXXABI().getThisArgumentTypeForMethod(MD); + Args.add(RValue::get(This), CGF.getTypes().DeriveThisType(RD, MD)); + + // If there is an implicit parameter (e.g. VTT), emit it. + if (ImplicitParam) { + llvm_unreachable("NYI"); + } + + const auto *FPT = MD->getType()->castAs(); + RequiredArgs required = RequiredArgs::forPrototypePlus(FPT, Args.size()); + unsigned PrefixSize = Args.size() - 1; + + // Add the rest of the call args + if (RtlArgs) { + // Special case: if the caller emitted the arguments right-to-left already + // (prior to emitting the *this argument), we're done. This happens for + // assignment operators. + Args.addFrom(*RtlArgs); + } else if (CE) { + // Special case: skip first argument of CXXOperatorCall (it is "this"). + unsigned ArgsToSkip = isa(CE) ? 1 : 0; + CGF.buildCallArgs(Args, FPT, drop_begin(CE->arguments(), ArgsToSkip), + CE->getDirectCallee()); + } else { + assert( + FPT->getNumParams() == 0 && + "No CallExpr specified for function with non-zero number of arguments"); + } + + return {required, PrefixSize}; +} + +RValue CIRGenFunction::buildCXXMemberOrOperatorCall( + const CXXMethodDecl *MD, const CIRGenCallee &Callee, + ReturnValueSlot ReturnValue, mlir::Value This, mlir::Value ImplicitParam, + QualType ImplicitParamTy, const CallExpr *CE, CallArgList *RtlArgs) { + + const auto *FPT = MD->getType()->castAs(); + CallArgList Args; + MemberCallInfo CallInfo = commonBuildCXXMemberOrOperatorCall( + *this, MD, This, ImplicitParam, ImplicitParamTy, CE, Args, RtlArgs); + auto &FnInfo = CGM.getTypes().arrangeCXXMethodCall( + Args, FPT, CallInfo.ReqArgs, CallInfo.PrefixSize); + assert((CE || currSrcLoc) && "expected source location"); + mlir::Location loc = CE ? getLoc(CE->getExprLoc()) : *currSrcLoc; + return buildCall(FnInfo, Callee, ReturnValue, Args, nullptr, + CE && CE == MustTailCall, loc); +} + +// TODO(cir): this can be shared with LLVM codegen +static CXXRecordDecl *getCXXRecord(const Expr *E) { + QualType T = E->getType(); + if (const PointerType *PTy = T->getAs()) + T = PTy->getPointeeType(); + const RecordType *Ty = T->castAs(); + return cast(Ty->getDecl()); +} + +RValue CIRGenFunction::buildCXXMemberOrOperatorMemberCallExpr( + const CallExpr *CE, const CXXMethodDecl *MD, ReturnValueSlot ReturnValue, + bool HasQualifier, NestedNameSpecifier *Qualifier, bool IsArrow, + const Expr *Base) { + assert(isa(CE) || isa(CE)); + + // Compute the object pointer. + bool CanUseVirtualCall = MD->isVirtual() && !HasQualifier; + const CXXMethodDecl *DevirtualizedMethod = nullptr; + if (CanUseVirtualCall && + MD->getDevirtualizedMethod(Base, getLangOpts().AppleKext)) { + const CXXRecordDecl *BestDynamicDecl = Base->getBestDynamicClassType(); + DevirtualizedMethod = MD->getCorrespondingMethodInClass(BestDynamicDecl); + assert(DevirtualizedMethod); + const CXXRecordDecl *DevirtualizedClass = DevirtualizedMethod->getParent(); + const Expr *Inner = Base->IgnoreParenBaseCasts(); + if (DevirtualizedMethod->getReturnType().getCanonicalType() != + MD->getReturnType().getCanonicalType()) { + // If the return types are not the same, this might be a case where more + // code needs to run to compensate for it. For example, the derived + // method might return a type that inherits form from the return + // type of MD and has a prefix. + // For now we just avoid devirtualizing these covariant cases. + DevirtualizedMethod = nullptr; + } else if (getCXXRecord(Inner) == DevirtualizedClass) { + // If the class of the Inner expression is where the dynamic method + // is defined, build the this pointer from it. + Base = Inner; + } else if (getCXXRecord(Base) != DevirtualizedClass) { + // If the method is defined in a class that is not the best dynamic + // one or the one of the full expression, we would have to build + // a derived-to-base cast to compute the correct this pointer, but + // we don't have support for that yet, so do a virtual call. + assert(!UnimplementedFeature::buildDerivedToBaseCastForDevirt()); + DevirtualizedMethod = nullptr; + } + } + + bool TrivialForCodegen = + MD->isTrivial() || (MD->isDefaulted() && MD->getParent()->isUnion()); + bool TrivialAssignment = + TrivialForCodegen && + (MD->isCopyAssignmentOperator() || MD->isMoveAssignmentOperator()) && + !MD->getParent()->mayInsertExtraPadding(); + (void)TrivialAssignment; + + // C++17 demands that we evaluate the RHS of a (possibly-compound) assignment + // operator before the LHS. + CallArgList RtlArgStorage; + CallArgList *RtlArgs = nullptr; + LValue TrivialAssignmentRHS; + if (auto *OCE = dyn_cast(CE)) { + if (OCE->isAssignmentOp()) { + // See further note on TrivialAssignment, we don't handle this during + // codegen, differently than LLVM, which early optimizes like this: + // if (TrivialAssignment) { + // TrivialAssignmentRHS = buildLValue(CE->getArg(1)); + // } else { + RtlArgs = &RtlArgStorage; + buildCallArgs(*RtlArgs, MD->getType()->castAs(), + drop_begin(CE->arguments(), 1), CE->getDirectCallee(), + /*ParamsToSkip*/ 0, EvaluationOrder::ForceRightToLeft); + } + } + + LValue This; + if (IsArrow) { + LValueBaseInfo BaseInfo; + assert(!UnimplementedFeature::tbaa()); + Address ThisValue = buildPointerWithAlignment(Base, &BaseInfo); + This = makeAddrLValue(ThisValue, Base->getType(), BaseInfo); + } else { + This = buildLValue(Base); + } + + if (const CXXConstructorDecl *Ctor = dyn_cast(MD)) { + llvm_unreachable("NYI"); + } + + if (TrivialForCodegen) { + if (isa(MD)) + return RValue::get(nullptr); + + if (TrivialAssignment) { + // From LLVM codegen: + // We don't like to generate the trivial copy/move assignment operator + // when it isn't necessary; just produce the proper effect here. + // It's important that we use the result of EmitLValue here rather than + // emitting call arguments, in order to preserve TBAA information from + // the RHS. + // + // We don't early optimize like LLVM does: + // LValue RHS = isa(CE) ? TrivialAssignmentRHS + // : + // buildLValue(*CE->arg_begin()); + // buildAggregateAssign(This, RHS, CE->getType()); + // return RValue::get(This.getPointer()); + } else { + assert(MD->getParent()->mayInsertExtraPadding() && + "unknown trivial member function"); + } + } + + // Compute the function type we're calling + const CXXMethodDecl *CalleeDecl = + DevirtualizedMethod ? DevirtualizedMethod : MD; + const CIRGenFunctionInfo *FInfo = nullptr; + if (const auto *Dtor = dyn_cast(CalleeDecl)) + llvm_unreachable("NYI"); + else + FInfo = &CGM.getTypes().arrangeCXXMethodDeclaration(CalleeDecl); + + auto Ty = CGM.getTypes().GetFunctionType(*FInfo); + + // C++11 [class.mfct.non-static]p2: + // If a non-static member function of a class X is called for an object that + // is not of type X, or of a type derived from X, the behavior is undefined. + SourceLocation CallLoc; + ASTContext &C = getContext(); + (void)C; + if (CE) + CallLoc = CE->getExprLoc(); + + SanitizerSet SkippedChecks; + if (const auto *cmce = dyn_cast(CE)) { + auto *ioa = cmce->getImplicitObjectArgument(); + auto isImplicitObjectCXXThis = isWrappedCXXThis(ioa); + if (isImplicitObjectCXXThis) + SkippedChecks.set(SanitizerKind::Alignment, true); + if (isImplicitObjectCXXThis || isa(ioa)) + SkippedChecks.set(SanitizerKind::Null, true); + } + + if (UnimplementedFeature::buildTypeCheck()) + llvm_unreachable("NYI"); + + // C++ [class.virtual]p12: + // Explicit qualification with the scope operator (5.1) suppresses the + // virtual call mechanism. + // + // We also don't emit a virtual call if the base expression has a record type + // because then we know what the type is. + bool useVirtualCall = CanUseVirtualCall && !DevirtualizedMethod; + + if (const auto *dtor = dyn_cast(CalleeDecl)) { + llvm_unreachable("NYI"); + } + + // FIXME: Uses of 'MD' past this point need to be audited. We may need to use + // 'CalleeDecl' instead. + + CIRGenCallee Callee; + if (useVirtualCall) { + Callee = CIRGenCallee::forVirtual(CE, MD, This.getAddress(), Ty); + } else { + if (SanOpts.has(SanitizerKind::CFINVCall)) { + llvm_unreachable("NYI"); + } + + if (getLangOpts().AppleKext) + llvm_unreachable("NYI"); + else if (!DevirtualizedMethod) + // TODO(cir): shouldn't this call getAddrOfCXXStructor instead? + Callee = CIRGenCallee::forDirect(CGM.GetAddrOfFunction(MD, Ty), + GlobalDecl(MD)); + else { + Callee = CIRGenCallee::forDirect(CGM.GetAddrOfFunction(MD, Ty), + GlobalDecl(MD)); + } + } + + if (MD->isVirtual()) { + Address NewThisAddr = + CGM.getCXXABI().adjustThisArgumentForVirtualFunctionCall( + *this, CalleeDecl, This.getAddress(), useVirtualCall); + This.setAddress(NewThisAddr); + } + + return buildCXXMemberOrOperatorCall( + CalleeDecl, Callee, ReturnValue, This.getPointer(), + /*ImplicitParam=*/nullptr, QualType(), CE, RtlArgs); +} + +RValue +CIRGenFunction::buildCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *E, + const CXXMethodDecl *MD, + ReturnValueSlot ReturnValue) { + assert(MD->isInstance() && + "Trying to emit a member call expr on a static method!"); + return buildCXXMemberOrOperatorMemberCallExpr( + E, MD, ReturnValue, /*HasQualifier=*/false, /*Qualifier=*/nullptr, + /*IsArrow=*/false, E->getArg(0)); +} + +void CIRGenFunction::buildCXXConstructExpr(const CXXConstructExpr *E, + AggValueSlot Dest) { + assert(!Dest.isIgnored() && "Must have a destination!"); + const auto *CD = E->getConstructor(); + + // If we require zero initialization before (or instead of) calling the + // constructor, as can be the case with a non-user-provided default + // constructor, emit the zero initialization now, unless destination is + // already zeroed. + if (E->requiresZeroInitialization() && !Dest.isZeroed()) { + switch (E->getConstructionKind()) { + case CXXConstructExpr::CK_Delegating: + case CXXConstructExpr::CK_Complete: + buildNullInitialization(getLoc(E->getSourceRange()), Dest.getAddress(), + E->getType()); + break; + case CXXConstructExpr::CK_VirtualBase: + case CXXConstructExpr::CK_NonVirtualBase: + llvm_unreachable("NYI"); + break; + } + } + + // If this is a call to a trivial default constructor: + // In LLVM: do nothing. + // In CIR: emit as a regular call, other later passes should lower the + // ctor call into trivial initialization. + // if (CD->isTrivial() && CD->isDefaultConstructor()) + // return; + + // Elide the constructor if we're constructing from a temporary + if (getLangOpts().ElideConstructors && E->isElidable()) { + // FIXME: This only handles the simplest case, where the source object is + // passed directly as the first argument to the constructor. This + // should also handle stepping through implicit casts and conversion + // sequences which involve two steps, with a conversion operator + // follwed by a converting constructor. + const auto *SrcObj = E->getArg(0); + assert(SrcObj->isTemporaryObject(getContext(), CD->getParent())); + assert( + getContext().hasSameUnqualifiedType(E->getType(), SrcObj->getType())); + buildAggExpr(SrcObj, Dest); + return; + } + + assert(!CGM.getASTContext().getAsArrayType(E->getType()) && + "array types NYI"); + + clang::CXXCtorType Type = Ctor_Complete; + bool ForVirtualBase = false; + bool Delegating = false; + + switch (E->getConstructionKind()) { + case CXXConstructExpr::CK_Complete: + Type = Ctor_Complete; + break; + case CXXConstructExpr::CK_Delegating: + llvm_unreachable("NYI"); + break; + case CXXConstructExpr::CK_VirtualBase: + ForVirtualBase = true; + [[fallthrough]]; + case CXXConstructExpr::CK_NonVirtualBase: + Type = Ctor_Base; + break; + } + + buildCXXConstructorCall(CD, Type, ForVirtualBase, Delegating, Dest, E); +} + +namespace { +/// The parameters to pass to a usual operator delete. +struct UsualDeleteParams { + bool DestroyingDelete = false; + bool Size = false; + bool Alignment = false; +}; +} // namespace + +// FIXME(cir): this should be shared with LLVM codegen +static UsualDeleteParams getUsualDeleteParams(const FunctionDecl *FD) { + UsualDeleteParams Params; + + const FunctionProtoType *FPT = FD->getType()->castAs(); + auto AI = FPT->param_type_begin(), AE = FPT->param_type_end(); + + // The first argument is always a void*. + ++AI; + + // The next parameter may be a std::destroying_delete_t. + if (FD->isDestroyingOperatorDelete()) { + Params.DestroyingDelete = true; + assert(AI != AE); + ++AI; + } + + // Figure out what other parameters we should be implicitly passing. + if (AI != AE && (*AI)->isIntegerType()) { + Params.Size = true; + ++AI; + } + + if (AI != AE && (*AI)->isAlignValT()) { + Params.Alignment = true; + ++AI; + } + + assert(AI == AE && "unexpected usual deallocation function parameter"); + return Params; +} + +static mlir::Value buildCXXNewAllocSize(CIRGenFunction &CGF, + const CXXNewExpr *e, + unsigned minElements, + mlir::Value &numElements, + mlir::Value &sizeWithoutCookie) { + QualType type = e->getAllocatedType(); + + if (!e->isArray()) { + CharUnits typeSize = CGF.getContext().getTypeSizeInChars(type); + sizeWithoutCookie = CGF.getBuilder().getConstant( + CGF.getLoc(e->getSourceRange()), + mlir::cir::IntAttr::get(CGF.SizeTy, typeSize.getQuantity())); + return sizeWithoutCookie; + } + + llvm_unreachable("NYI"); +} + +namespace { +/// A cleanup to call the given 'operator delete' function upon abnormal +/// exit from a new expression. Templated on a traits type that deals with +/// ensuring that the arguments dominate the cleanup if necessary. +template +class CallDeleteDuringNew final : public EHScopeStack::Cleanup { + /// Type used to hold llvm::Value*s. + typedef typename Traits::ValueTy ValueTy; + /// Type used to hold RValues. + typedef typename Traits::RValueTy RValueTy; + struct PlacementArg { + RValueTy ArgValue; + QualType ArgType; + }; + + unsigned NumPlacementArgs : 31; + unsigned PassAlignmentToPlacementDelete : 1; + const FunctionDecl *OperatorDelete; + ValueTy Ptr; + ValueTy AllocSize; + CharUnits AllocAlign; + + PlacementArg *getPlacementArgs() { + return reinterpret_cast(this + 1); + } + +public: + static size_t getExtraSize(size_t NumPlacementArgs) { + return NumPlacementArgs * sizeof(PlacementArg); + } + + CallDeleteDuringNew(size_t NumPlacementArgs, + const FunctionDecl *OperatorDelete, ValueTy Ptr, + ValueTy AllocSize, bool PassAlignmentToPlacementDelete, + CharUnits AllocAlign) + : NumPlacementArgs(NumPlacementArgs), + PassAlignmentToPlacementDelete(PassAlignmentToPlacementDelete), + OperatorDelete(OperatorDelete), Ptr(Ptr), AllocSize(AllocSize), + AllocAlign(AllocAlign) {} + + void setPlacementArg(unsigned I, RValueTy Arg, QualType Type) { + assert(I < NumPlacementArgs && "index out of range"); + getPlacementArgs()[I] = {Arg, Type}; + } + + void Emit(CIRGenFunction &CGF, Flags flags) override { + const auto *FPT = OperatorDelete->getType()->castAs(); + CallArgList DeleteArgs; + + // The first argument is always a void* (or C* for a destroying operator + // delete for class type C). + DeleteArgs.add(Traits::get(CGF, Ptr), FPT->getParamType(0)); + + // Figure out what other parameters we should be implicitly passing. + UsualDeleteParams Params; + if (NumPlacementArgs) { + // A placement deallocation function is implicitly passed an alignment + // if the placement allocation function was, but is never passed a size. + Params.Alignment = PassAlignmentToPlacementDelete; + } else { + // For a non-placement new-expression, 'operator delete' can take a + // size and/or an alignment if it has the right parameters. + Params = getUsualDeleteParams(OperatorDelete); + } + + assert(!Params.DestroyingDelete && + "should not call destroying delete in a new-expression"); + + // The second argument can be a std::size_t (for non-placement delete). + if (Params.Size) + DeleteArgs.add(Traits::get(CGF, AllocSize), + CGF.getContext().getSizeType()); + + // The next (second or third) argument can be a std::align_val_t, which + // is an enum whose underlying type is std::size_t. + // FIXME: Use the right type as the parameter type. Note that in a call + // to operator delete(size_t, ...), we may not have it available. + if (Params.Alignment) { + llvm_unreachable("NYI"); + } + + // Pass the rest of the arguments, which must match exactly. + for (unsigned I = 0; I != NumPlacementArgs; ++I) { + auto Arg = getPlacementArgs()[I]; + DeleteArgs.add(Traits::get(CGF, Arg.ArgValue), Arg.ArgType); + } + + // Call 'operator delete'. + buildNewDeleteCall(CGF, OperatorDelete, FPT, DeleteArgs); + } +}; +} // namespace + +/// Enter a cleanup to call 'operator delete' if the initializer in a +/// new-expression throws. +static void EnterNewDeleteCleanup(CIRGenFunction &CGF, const CXXNewExpr *E, + Address NewPtr, mlir::Value AllocSize, + CharUnits AllocAlign, + const CallArgList &NewArgs) { + unsigned NumNonPlacementArgs = E->passAlignment() ? 2 : 1; + + // If we're not inside a conditional branch, then the cleanup will + // dominate and we can do the easier (and more efficient) thing. + if (!CGF.isInConditionalBranch()) { + struct DirectCleanupTraits { + typedef mlir::Value ValueTy; + typedef RValue RValueTy; + static RValue get(CIRGenFunction &, ValueTy V) { return RValue::get(V); } + static RValue get(CIRGenFunction &, RValueTy V) { return V; } + }; + + typedef CallDeleteDuringNew DirectCleanup; + + DirectCleanup *Cleanup = CGF.EHStack.pushCleanupWithExtra( + EHCleanup, E->getNumPlacementArgs(), E->getOperatorDelete(), + NewPtr.getPointer(), AllocSize, E->passAlignment(), AllocAlign); + for (unsigned I = 0, N = E->getNumPlacementArgs(); I != N; ++I) { + auto &Arg = NewArgs[I + NumNonPlacementArgs]; + Cleanup->setPlacementArg( + I, Arg.getRValue(CGF, CGF.getLoc(E->getSourceRange())), Arg.Ty); + } + + return; + } + + // Otherwise, we need to save all this stuff. + DominatingValue::saved_type SavedNewPtr = + DominatingValue::save(CGF, RValue::get(NewPtr.getPointer())); + DominatingValue::saved_type SavedAllocSize = + DominatingValue::save(CGF, RValue::get(AllocSize)); + + struct ConditionalCleanupTraits { + typedef DominatingValue::saved_type ValueTy; + typedef DominatingValue::saved_type RValueTy; + static RValue get(CIRGenFunction &CGF, ValueTy V) { return V.restore(CGF); } + }; + typedef CallDeleteDuringNew ConditionalCleanup; + + ConditionalCleanup *Cleanup = + CGF.EHStack.pushCleanupWithExtra( + EHCleanup, E->getNumPlacementArgs(), E->getOperatorDelete(), + SavedNewPtr, SavedAllocSize, E->passAlignment(), AllocAlign); + for (unsigned I = 0, N = E->getNumPlacementArgs(); I != N; ++I) { + auto &Arg = NewArgs[I + NumNonPlacementArgs]; + Cleanup->setPlacementArg( + I, + DominatingValue::save( + CGF, Arg.getRValue(CGF, CGF.getLoc(E->getSourceRange()))), + Arg.Ty); + } + + CGF.initFullExprCleanup(); +} + +static void StoreAnyExprIntoOneUnit(CIRGenFunction &CGF, const Expr *Init, + QualType AllocType, Address NewPtr, + AggValueSlot::Overlap_t MayOverlap) { + // FIXME: Refactor with buildExprAsInit. + switch (CGF.getEvaluationKind(AllocType)) { + case TEK_Scalar: + CGF.buildScalarInit(Init, CGF.getLoc(Init->getSourceRange()), + CGF.makeAddrLValue(NewPtr, AllocType), false); + return; + case TEK_Complex: + llvm_unreachable("NYI"); + return; + case TEK_Aggregate: { + AggValueSlot Slot = AggValueSlot::forAddr( + NewPtr, AllocType.getQualifiers(), AggValueSlot::IsDestructed, + AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased, + MayOverlap, AggValueSlot::IsNotZeroed, + AggValueSlot::IsSanitizerChecked); + CGF.buildAggExpr(Init, Slot); + return; + } + } + llvm_unreachable("bad evaluation kind"); +} + +static void buildNewInitializer(CIRGenFunction &CGF, const CXXNewExpr *E, + QualType ElementType, mlir::Type ElementTy, + Address NewPtr, mlir::Value NumElements, + mlir::Value AllocSizeWithoutCookie) { + assert(!UnimplementedFeature::generateDebugInfo()); + if (E->isArray()) { + llvm_unreachable("NYI"); + } else if (const Expr *Init = E->getInitializer()) { + StoreAnyExprIntoOneUnit(CGF, Init, E->getAllocatedType(), NewPtr, + AggValueSlot::DoesNotOverlap); + } +} + +static CharUnits CalculateCookiePadding(CIRGenFunction &CGF, + const CXXNewExpr *E) { + if (!E->isArray()) + return CharUnits::Zero(); + + // No cookie is required if the operator new[] being used is the + // reserved placement operator new[]. + if (E->getOperatorNew()->isReservedGlobalPlacementOperator()) + return CharUnits::Zero(); + + llvm_unreachable("NYI"); + // return CGF.CGM.getCXXABI().GetArrayCookieSize(E); +} + +mlir::Value CIRGenFunction::buildCXXNewExpr(const CXXNewExpr *E) { + // The element type being allocated. + QualType allocType = getContext().getBaseElementType(E->getAllocatedType()); + + // 1. Build a call to the allocation function. + FunctionDecl *allocator = E->getOperatorNew(); + + // If there is a brace-initializer, cannot allocate fewer elements than inits. + unsigned minElements = 0; + if (E->isArray() && E->hasInitializer()) { + const InitListExpr *ILE = dyn_cast(E->getInitializer()); + if (ILE && ILE->isStringLiteralInit()) + minElements = + cast(ILE->getType()->getAsArrayTypeUnsafe()) + ->getSize() + .getZExtValue(); + else if (ILE) + minElements = ILE->getNumInits(); + } + + mlir::Value numElements = nullptr; + mlir::Value allocSizeWithoutCookie = nullptr; + mlir::Value allocSize = buildCXXNewAllocSize( + *this, E, minElements, numElements, allocSizeWithoutCookie); + CharUnits allocAlign = getContext().getTypeAlignInChars(allocType); + + // Emit the allocation call. + Address allocation = Address::invalid(); + CallArgList allocatorArgs; + if (allocator->isReservedGlobalPlacementOperator()) { + // If the allocator is a global placement operator, just + // "inline" it directly. + assert(E->getNumPlacementArgs() == 1); + const Expr *arg = *E->placement_arguments().begin(); + + LValueBaseInfo BaseInfo; + allocation = buildPointerWithAlignment(arg, &BaseInfo); + + // The pointer expression will, in many cases, be an opaque void*. + // In these cases, discard the computed alignment and use the + // formal alignment of the allocated type. + if (BaseInfo.getAlignmentSource() != AlignmentSource::Decl) + allocation = allocation.withAlignment(allocAlign); + + // Set up allocatorArgs for the call to operator delete if it's not + // the reserved global operator. + if (E->getOperatorDelete() && + !E->getOperatorDelete()->isReservedGlobalPlacementOperator()) { + allocatorArgs.add(RValue::get(allocSize), getContext().getSizeType()); + allocatorArgs.add(RValue::get(allocation.getPointer()), arg->getType()); + } + } else { + const FunctionProtoType *allocatorType = + allocator->getType()->castAs(); + unsigned ParamsToSkip = 0; + + // The allocation size is the first argument. + QualType sizeType = getContext().getSizeType(); + allocatorArgs.add(RValue::get(allocSize), sizeType); + ++ParamsToSkip; + + if (allocSize != allocSizeWithoutCookie) { + llvm_unreachable("NYI"); + } + + // The allocation alignment may be passed as the second argument. + if (E->passAlignment()) { + llvm_unreachable("NYI"); + } + + // FIXME: Why do we not pass a CalleeDecl here? + buildCallArgs(allocatorArgs, allocatorType, E->placement_arguments(), + /*AC*/ + AbstractCallee(), + /*ParamsToSkip*/ + ParamsToSkip); + RValue RV = + buildNewDeleteCall(*this, allocator, allocatorType, allocatorArgs); + + // Set !heapallocsite metadata on the call to operator new. + assert(!UnimplementedFeature::generateDebugInfo()); + + // If this was a call to a global replaceable allocation function that does + // not take an alignment argument, the allocator is known to produce storage + // that's suitably aligned for any object that fits, up to a known + // threshold. Otherwise assume it's suitably aligned for the allocated type. + CharUnits allocationAlign = allocAlign; + if (!E->passAlignment() && + allocator->isReplaceableGlobalAllocationFunction()) { + auto &Target = CGM.getASTContext().getTargetInfo(); + unsigned AllocatorAlign = llvm::bit_floor(std::min( + Target.getNewAlign(), getContext().getTypeSize(allocType))); + allocationAlign = std::max( + allocationAlign, getContext().toCharUnitsFromBits(AllocatorAlign)); + } + + allocation = Address(RV.getScalarVal(), UInt8Ty, allocationAlign); + } + + // Emit a null check on the allocation result if the allocation + // function is allowed to return null (because it has a non-throwing + // exception spec or is the reserved placement new) and we have an + // interesting initializer will be running sanitizers on the initialization. + bool nullCheck = E->shouldNullCheckAllocation() && + (!allocType.isPODType(getContext()) || E->hasInitializer() || + sanitizePerformTypeCheck()); + + // The null-check means that the initializer is conditionally + // evaluated. + ConditionalEvaluation conditional(*this); + + if (nullCheck) { + llvm_unreachable("NYI"); + } + + // If there's an operator delete, enter a cleanup to call it if an + // exception is thrown. + EHScopeStack::stable_iterator operatorDeleteCleanup; + [[maybe_unused]] mlir::Operation *cleanupDominator = nullptr; + if (E->getOperatorDelete() && + !E->getOperatorDelete()->isReservedGlobalPlacementOperator()) { + EnterNewDeleteCleanup(*this, E, allocation, allocSize, allocAlign, + allocatorArgs); + operatorDeleteCleanup = EHStack.stable_begin(); + // FIXME: cleanupDominator = Builder.CreateUnreachable(); + } + + assert((allocSize == allocSizeWithoutCookie) == + CalculateCookiePadding(*this, E).isZero()); + if (allocSize != allocSizeWithoutCookie) { + llvm_unreachable("NYI"); + } + + mlir::Type elementTy = getTypes().convertTypeForMem(allocType); + Address result = builder.createElementBitCast(getLoc(E->getSourceRange()), + allocation, elementTy); + + // Passing pointer through launder.invariant.group to avoid propagation of + // vptrs information which may be included in previous type. + // To not break LTO with different optimizations levels, we do it regardless + // of optimization level. + if (CGM.getCodeGenOpts().StrictVTablePointers && + allocator->isReservedGlobalPlacementOperator()) + llvm_unreachable("NYI"); + + // Emit sanitizer checks for pointer value now, so that in the case of an + // array it was checked only once and not at each constructor call. We may + // have already checked that the pointer is non-null. + // FIXME: If we have an array cookie and a potentially-throwing allocator, + // we'll null check the wrong pointer here. + SanitizerSet SkippedChecks; + SkippedChecks.set(SanitizerKind::Null, nullCheck); + buildTypeCheck(CIRGenFunction::TCK_ConstructorCall, + E->getAllocatedTypeSourceInfo()->getTypeLoc().getBeginLoc(), + result.getPointer(), allocType, result.getAlignment(), + SkippedChecks, numElements); + + buildNewInitializer(*this, E, allocType, elementTy, result, numElements, + allocSizeWithoutCookie); + auto resultPtr = result.getPointer(); + if (E->isArray()) { + llvm_unreachable("NYI"); + } + + // Deactivate the 'operator delete' cleanup if we finished + // initialization. + if (operatorDeleteCleanup.isValid()) { + // FIXME: enable cleanupDominator above before implementing this. + DeactivateCleanupBlock(operatorDeleteCleanup, cleanupDominator); + if (cleanupDominator) + cleanupDominator->erase(); + } + + if (nullCheck) { + llvm_unreachable("NYI"); + } + + return resultPtr; +} + +RValue CIRGenFunction::buildCXXDestructorCall(GlobalDecl Dtor, + const CIRGenCallee &Callee, + mlir::Value This, QualType ThisTy, + mlir::Value ImplicitParam, + QualType ImplicitParamTy, + const CallExpr *CE) { + const CXXMethodDecl *DtorDecl = cast(Dtor.getDecl()); + + assert(!ThisTy.isNull()); + assert(ThisTy->getAsCXXRecordDecl() == DtorDecl->getParent() && + "Pointer/Object mixup"); + + LangAS SrcAS = ThisTy.getAddressSpace(); + LangAS DstAS = DtorDecl->getMethodQualifiers().getAddressSpace(); + if (SrcAS != DstAS) { + llvm_unreachable("NYI"); + } + + CallArgList Args; + commonBuildCXXMemberOrOperatorCall(*this, DtorDecl, This, ImplicitParam, + ImplicitParamTy, CE, Args, nullptr); + assert((CE || Dtor.getDecl()) && "expected source location provider"); + return buildCall(CGM.getTypes().arrangeCXXStructorDeclaration(Dtor), Callee, + ReturnValueSlot(), Args, nullptr, CE && CE == MustTailCall, + CE ? getLoc(CE->getExprLoc()) + : getLoc(Dtor.getDecl()->getSourceRange())); +} + +/// Emit a call to an operator new or operator delete function, as implicitly +/// created by new-expressions and delete-expressions. +static RValue buildNewDeleteCall(CIRGenFunction &CGF, + const FunctionDecl *CalleeDecl, + const FunctionProtoType *CalleeType, + const CallArgList &Args) { + mlir::cir::CallOp CallOrInvoke{}; + auto CalleePtr = CGF.CGM.GetAddrOfFunction(CalleeDecl); + CIRGenCallee Callee = + CIRGenCallee::forDirect(CalleePtr, GlobalDecl(CalleeDecl)); + RValue RV = CGF.buildCall(CGF.CGM.getTypes().arrangeFreeFunctionCall( + Args, CalleeType, /*ChainCall=*/false), + Callee, ReturnValueSlot(), Args, &CallOrInvoke); + + /// C++1y [expr.new]p10: + /// [In a new-expression,] an implementation is allowed to omit a call + /// to a replaceable global allocation function. + /// + /// We model such elidable calls with the 'builtin' attribute. + assert(!UnimplementedFeature::attributeBuiltin()); + return RV; +} + +void CIRGenFunction::buildDeleteCall(const FunctionDecl *DeleteFD, + mlir::Value Ptr, QualType DeleteTy, + mlir::Value NumElements, + CharUnits CookieSize) { + assert((!NumElements && CookieSize.isZero()) || + DeleteFD->getOverloadedOperator() == OO_Array_Delete); + + const auto *DeleteFTy = DeleteFD->getType()->castAs(); + CallArgList DeleteArgs; + + auto Params = getUsualDeleteParams(DeleteFD); + auto ParamTypeIt = DeleteFTy->param_type_begin(); + + // Pass the pointer itself. + QualType ArgTy = *ParamTypeIt++; + mlir::Value DeletePtr = + builder.createBitcast(Ptr.getLoc(), Ptr, ConvertType(ArgTy)); + DeleteArgs.add(RValue::get(DeletePtr), ArgTy); + + // Pass the std::destroying_delete tag if present. + mlir::Value DestroyingDeleteTag{}; + if (Params.DestroyingDelete) { + llvm_unreachable("NYI"); + } + + // Pass the size if the delete function has a size_t parameter. + if (Params.Size) { + llvm_unreachable("NYI"); + } + + // Pass the alignment if the delete function has an align_val_t parameter. + if (Params.Alignment) { + llvm_unreachable("NYI"); + } + + assert(ParamTypeIt == DeleteFTy->param_type_end() && + "unknown parameter to usual delete function"); + + // Emit the call to delete. + buildNewDeleteCall(*this, DeleteFD, DeleteFTy, DeleteArgs); + + // If call argument lowering didn't use the destroying_delete_t alloca, + // remove it again. + if (DestroyingDeleteTag && DestroyingDeleteTag.use_empty()) { + llvm_unreachable("NYI"); // DestroyingDeleteTag->eraseFromParent(); + } +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp new file mode 100644 index 000000000000..48f763c8bb0c --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp @@ -0,0 +1,1566 @@ +//===---- CIRGenExprCst.cpp - Emit LLVM Code from Constant Expressions ----===// +// +// 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 "CIRDataLayout.h" +#include "CIRGenCstEmitter.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/RecordLayout.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/Builtins.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" +#include + +using namespace clang; +using namespace cir; + +//===----------------------------------------------------------------------===// +// ConstantAggregateBuilder +//===----------------------------------------------------------------------===// + +namespace { +class ConstExprEmitter; + +static mlir::Attribute +buildArrayConstant(CIRGenModule &CGM, mlir::Type DesiredType, + mlir::Type CommonElementType, unsigned ArrayBound, + SmallVectorImpl &Elements, + mlir::TypedAttr Filler); + +struct ConstantAggregateBuilderUtils { + CIRGenModule &CGM; + CIRDataLayout dataLayout; + + ConstantAggregateBuilderUtils(CIRGenModule &CGM) + : CGM(CGM), dataLayout{CGM.getModule()} {} + + CharUnits getAlignment(const mlir::TypedAttr C) const { + return CharUnits::fromQuantity( + dataLayout.getAlignment(C.getType(), /*useABI=*/true)); + } + + CharUnits getSize(mlir::Type Ty) const { + return CharUnits::fromQuantity(dataLayout.getTypeAllocSize(Ty)); + } + + CharUnits getSize(const mlir::TypedAttr C) const { + return getSize(C.getType()); + } + + mlir::Attribute getPadding(CharUnits PadSize) const { + llvm_unreachable("NYI"); + } + + mlir::Attribute getZeroes(CharUnits ZeroSize) const { + llvm_unreachable("NYI"); + } +}; + +/// Incremental builder for an mlir::TypedAttr holding a struct or array +/// constant. +class ConstantAggregateBuilder : private ConstantAggregateBuilderUtils { + /// The elements of the constant. These two arrays must have the same size; + /// Offsets[i] describes the offset of Elems[i] within the constant. The + /// elements are kept in increasing offset order, and we ensure that there + /// is no overlap: Offsets[i+1] >= Offsets[i] + getSize(Elemes[i]). + /// + /// This may contain explicit padding elements (in order to create a + /// natural layout), but need not. Gaps between elements are implicitly + /// considered to be filled with undef. + llvm::SmallVector Elems; + llvm::SmallVector Offsets; + + /// The size of the constant (the maximum end offset of any added element). + /// May be larger than the end of Elems.back() if we split the last element + /// and removed some trailing undefs. + CharUnits Size = CharUnits::Zero(); + + /// This is true only if laying out Elems in order as the elements of a + /// non-packed LLVM struct will give the correct layout. + bool NaturalLayout = true; + + bool split(size_t Index, CharUnits Hint); + std::optional splitAt(CharUnits Pos); + + static mlir::Attribute + buildFrom(CIRGenModule &CGM, ArrayRef Elems, + ArrayRef Offsets, CharUnits StartOffset, CharUnits Size, + bool NaturalLayout, mlir::Type DesiredTy, bool AllowOversized); + +public: + ConstantAggregateBuilder(CIRGenModule &CGM) + : ConstantAggregateBuilderUtils(CGM) {} + + /// Update or overwrite the value starting at \p Offset with \c C. + /// + /// \param AllowOverwrite If \c true, this constant might overwrite (part of) + /// a constant that has already been added. This flag is only used to + /// detect bugs. + bool add(mlir::Attribute C, CharUnits Offset, bool AllowOverwrite); + + /// Update or overwrite the bits starting at \p OffsetInBits with \p Bits. + bool addBits(llvm::APInt Bits, uint64_t OffsetInBits, bool AllowOverwrite); + + /// Attempt to condense the value starting at \p Offset to a constant of type + /// \p DesiredTy. + void condense(CharUnits Offset, mlir::Type DesiredTy); + + /// Produce a constant representing the entire accumulated value, ideally of + /// the specified type. If \p AllowOversized, the constant might be larger + /// than implied by \p DesiredTy (eg, if there is a flexible array member). + /// Otherwise, the constant will be of exactly the same size as \p DesiredTy + /// even if we can't represent it as that type. + mlir::Attribute build(mlir::Type DesiredTy, bool AllowOversized) const { + return buildFrom(CGM, Elems, Offsets, CharUnits::Zero(), Size, + NaturalLayout, DesiredTy, AllowOversized); + } +}; + +template > +static void replace(Container &C, size_t BeginOff, size_t EndOff, Range Vals) { + assert(BeginOff <= EndOff && "invalid replacement range"); + llvm::replace(C, C.begin() + BeginOff, C.begin() + EndOff, Vals); +} + +bool ConstantAggregateBuilder::add(mlir::Attribute A, CharUnits Offset, + bool AllowOverwrite) { + // FIXME(cir): migrate most of this file to use mlir::TypedAttr directly. + mlir::TypedAttr C = A.dyn_cast(); + assert(C && "expected typed attribute"); + // Common case: appending to a layout. + if (Offset >= Size) { + CharUnits Align = getAlignment(C); + CharUnits AlignedSize = Size.alignTo(Align); + if (AlignedSize > Offset || Offset.alignTo(Align) != Offset) + NaturalLayout = false; + else if (AlignedSize < Offset) { + Elems.push_back(getPadding(Offset - Size)); + Offsets.push_back(Size); + } + Elems.push_back(C); + Offsets.push_back(Offset); + Size = Offset + getSize(C); + return true; + } + + // Uncommon case: constant overlaps what we've already created. + std::optional FirstElemToReplace = splitAt(Offset); + if (!FirstElemToReplace) + return false; + + CharUnits CSize = getSize(C); + std::optional LastElemToReplace = splitAt(Offset + CSize); + if (!LastElemToReplace) + return false; + + assert((FirstElemToReplace == LastElemToReplace || AllowOverwrite) && + "unexpectedly overwriting field"); + + replace(Elems, *FirstElemToReplace, *LastElemToReplace, {C}); + replace(Offsets, *FirstElemToReplace, *LastElemToReplace, {Offset}); + Size = std::max(Size, Offset + CSize); + NaturalLayout = false; + return true; +} + +bool ConstantAggregateBuilder::addBits(llvm::APInt Bits, uint64_t OffsetInBits, + bool AllowOverwrite) { + llvm_unreachable("NYI"); +} + +/// Returns a position within Elems and Offsets such that all elements +/// before the returned index end before Pos and all elements at or after +/// the returned index begin at or after Pos. Splits elements as necessary +/// to ensure this. Returns None if we find something we can't split. +std::optional ConstantAggregateBuilder::splitAt(CharUnits Pos) { + if (Pos >= Size) + return Offsets.size(); + + while (true) { + auto FirstAfterPos = llvm::upper_bound(Offsets, Pos); + if (FirstAfterPos == Offsets.begin()) + return 0; + + // If we already have an element starting at Pos, we're done. + size_t LastAtOrBeforePosIndex = FirstAfterPos - Offsets.begin() - 1; + if (Offsets[LastAtOrBeforePosIndex] == Pos) + return LastAtOrBeforePosIndex; + + // We found an element starting before Pos. Check for overlap. + // FIXME(cir): migrate most of this file to use mlir::TypedAttr directly. + mlir::TypedAttr C = + Elems[LastAtOrBeforePosIndex].dyn_cast(); + assert(C && "expected typed attribute"); + if (Offsets[LastAtOrBeforePosIndex] + getSize(C) <= Pos) + return LastAtOrBeforePosIndex + 1; + + // Try to decompose it into smaller constants. + if (!split(LastAtOrBeforePosIndex, Pos)) + return std::nullopt; + } +} + +/// Split the constant at index Index, if possible. Return true if we did. +/// Hint indicates the location at which we'd like to split, but may be +/// ignored. +bool ConstantAggregateBuilder::split(size_t Index, CharUnits Hint) { + llvm_unreachable("NYI"); +} + +mlir::Attribute ConstantAggregateBuilder::buildFrom( + CIRGenModule &CGM, ArrayRef Elems, + ArrayRef Offsets, CharUnits StartOffset, CharUnits Size, + bool NaturalLayout, mlir::Type DesiredTy, bool AllowOversized) { + ConstantAggregateBuilderUtils Utils(CGM); + + if (Elems.empty()) + return {}; + + // If we want an array type, see if all the elements are the same type and + // appropriately spaced. + if (auto aty = DesiredTy.dyn_cast()) { + llvm_unreachable("NYI"); + } + + // The size of the constant we plan to generate. This is usually just the size + // of the initialized type, but in AllowOversized mode (i.e. flexible array + // init), it can be larger. + CharUnits DesiredSize = Utils.getSize(DesiredTy); + if (Size > DesiredSize) { + assert(AllowOversized && "Elems are oversized"); + DesiredSize = Size; + } + + // The natural alignment of an unpacked CIR struct with the given elements. + CharUnits Align = CharUnits::One(); + for (auto e : Elems) { + // FIXME(cir): migrate most of this file to use mlir::TypedAttr directly. + auto C = e.dyn_cast(); + assert(C && "expected typed attribute"); + Align = std::max(Align, Utils.getAlignment(C)); + } + + // The natural size of an unpacked LLVM struct with the given elements. + CharUnits AlignedSize = Size.alignTo(Align); + + bool Packed = false; + ArrayRef UnpackedElems = Elems; + llvm::SmallVector UnpackedElemStorage; + if (DesiredSize < AlignedSize || DesiredSize.alignTo(Align) != DesiredSize) { + llvm_unreachable("NYI"); + } + + // If we don't have a natural layout, insert padding as necessary. + // As we go, double-check to see if we can actually just emit Elems + // as a non-packed struct and do so opportunistically if possible. + llvm::SmallVector PackedElems; + if (!NaturalLayout) { + llvm_unreachable("NYI"); + } + + // TODO(cir): emit a #cir.zero if all elements are null values. + auto &builder = CGM.getBuilder(); + auto arrAttr = mlir::ArrayAttr::get(builder.getContext(), + Packed ? PackedElems : UnpackedElems); + return builder.getConstStructOrZeroAttr(arrAttr, Packed, DesiredTy); +} + +void ConstantAggregateBuilder::condense(CharUnits Offset, + mlir::Type DesiredTy) { + CharUnits Size = getSize(DesiredTy); + + std::optional FirstElemToReplace = splitAt(Offset); + if (!FirstElemToReplace) + return; + size_t First = *FirstElemToReplace; + + std::optional LastElemToReplace = splitAt(Offset + Size); + if (!LastElemToReplace) + return; + size_t Last = *LastElemToReplace; + + size_t Length = Last - First; + if (Length == 0) + return; + + // FIXME(cir): migrate most of this file to use mlir::TypedAttr directly. + mlir::TypedAttr C = Elems[First].dyn_cast(); + assert(C && "expected typed attribute"); + if (Length == 1 && Offsets[First] == Offset && getSize(C) == Size) { + // Re-wrap single element structs if necessary. Otherwise, leave any single + // element constant of the right size alone even if it has the wrong type. + llvm_unreachable("NYI"); + } + + mlir::Attribute Replacement = buildFrom( + CGM, ArrayRef(Elems).slice(First, Length), + ArrayRef(Offsets).slice(First, Length), Offset, getSize(DesiredTy), + /*known to have natural layout=*/false, DesiredTy, false); + replace(Elems, First, Last, {Replacement}); + replace(Offsets, First, Last, {Offset}); +} + +//===----------------------------------------------------------------------===// +// ConstStructBuilder +//===----------------------------------------------------------------------===// + +class ConstStructBuilder { + CIRGenModule &CGM; + ConstantEmitter &Emitter; + ConstantAggregateBuilder &Builder; + CharUnits StartOffset; + +public: + static mlir::Attribute BuildStruct(ConstantEmitter &Emitter, + InitListExpr *ILE, QualType StructTy); + static mlir::Attribute BuildStruct(ConstantEmitter &Emitter, + const APValue &Value, QualType ValTy); + static bool UpdateStruct(ConstantEmitter &Emitter, + ConstantAggregateBuilder &Const, CharUnits Offset, + InitListExpr *Updater); + +private: + ConstStructBuilder(ConstantEmitter &Emitter, + ConstantAggregateBuilder &Builder, CharUnits StartOffset) + : CGM(Emitter.CGM), Emitter(Emitter), Builder(Builder), + StartOffset(StartOffset) {} + + bool AppendField(const FieldDecl *Field, uint64_t FieldOffset, + mlir::Attribute InitExpr, bool AllowOverwrite = false); + + bool AppendBytes(CharUnits FieldOffsetInChars, mlir::Attribute InitCst, + bool AllowOverwrite = false); + + bool AppendBitField(const FieldDecl *Field, uint64_t FieldOffset, + mlir::IntegerAttr InitExpr, bool AllowOverwrite = false); + + bool Build(InitListExpr *ILE, bool AllowOverwrite); + bool Build(const APValue &Val, const RecordDecl *RD, bool IsPrimaryBase, + const CXXRecordDecl *VTableClass, CharUnits BaseOffset); + mlir::Attribute Finalize(QualType Ty); +}; + +bool ConstStructBuilder::AppendField(const FieldDecl *Field, + uint64_t FieldOffset, + mlir::Attribute InitCst, + bool AllowOverwrite) { + const ASTContext &Context = CGM.getASTContext(); + + CharUnits FieldOffsetInChars = Context.toCharUnitsFromBits(FieldOffset); + + return AppendBytes(FieldOffsetInChars, InitCst, AllowOverwrite); +} + +bool ConstStructBuilder::AppendBytes(CharUnits FieldOffsetInChars, + mlir::Attribute InitCst, + bool AllowOverwrite) { + return Builder.add(InitCst, StartOffset + FieldOffsetInChars, AllowOverwrite); +} + +bool ConstStructBuilder::AppendBitField(const FieldDecl *Field, + uint64_t FieldOffset, + mlir::IntegerAttr CI, + bool AllowOverwrite) { + llvm_unreachable("NYI"); +} + +static bool EmitDesignatedInitUpdater(ConstantEmitter &Emitter, + ConstantAggregateBuilder &Const, + CharUnits Offset, QualType Type, + InitListExpr *Updater) { + if (Type->isRecordType()) + return ConstStructBuilder::UpdateStruct(Emitter, Const, Offset, Updater); + + auto CAT = Emitter.CGM.getASTContext().getAsConstantArrayType(Type); + if (!CAT) + return false; + QualType ElemType = CAT->getElementType(); + CharUnits ElemSize = Emitter.CGM.getASTContext().getTypeSizeInChars(ElemType); + mlir::Type ElemTy = Emitter.CGM.getTypes().convertTypeForMem(ElemType); + + mlir::Attribute FillC = nullptr; + if (Expr *Filler = Updater->getArrayFiller()) { + if (!isa(Filler)) { + llvm_unreachable("NYI"); + } + } + + unsigned NumElementsToUpdate = + FillC ? CAT->getSize().getZExtValue() : Updater->getNumInits(); + for (unsigned I = 0; I != NumElementsToUpdate; ++I, Offset += ElemSize) { + Expr *Init = nullptr; + if (I < Updater->getNumInits()) + Init = Updater->getInit(I); + + if (!Init && FillC) { + if (!Const.add(FillC, Offset, true)) + return false; + } else if (!Init || isa(Init)) { + continue; + } else if (InitListExpr *ChildILE = dyn_cast(Init)) { + if (!EmitDesignatedInitUpdater(Emitter, Const, Offset, ElemType, + ChildILE)) + return false; + // Attempt to reduce the array element to a single constant if necessary. + Const.condense(Offset, ElemTy); + } else { + mlir::Attribute Val = Emitter.tryEmitPrivateForMemory(Init, ElemType); + if (!Const.add(Val, Offset, true)) + return false; + } + } + + return true; +} + +bool ConstStructBuilder::Build(InitListExpr *ILE, bool AllowOverwrite) { + RecordDecl *RD = ILE->getType()->castAs()->getDecl(); + const ASTRecordLayout &Layout = CGM.getASTContext().getASTRecordLayout(RD); + + unsigned FieldNo = -1; + unsigned ElementNo = 0; + + // Bail out if we have base classes. We could support these, but they only + // arise in C++1z where we will have already constant folded most interesting + // cases. FIXME: There are still a few more cases we can handle this way. + if (auto *CXXRD = dyn_cast(RD)) + if (CXXRD->getNumBases()) + return false; + + for (FieldDecl *Field : RD->fields()) { + ++FieldNo; + + // If this is a union, skip all the fields that aren't being initialized. + if (RD->isUnion() && + !declaresSameEntity(ILE->getInitializedFieldInUnion(), Field)) + continue; + + // Don't emit anonymous bitfields. + if (Field->isUnnamedBitfield()) + continue; + + // Get the initializer. A struct can include fields without initializers, + // we just use explicit null values for them. + Expr *Init = nullptr; + if (ElementNo < ILE->getNumInits()) + Init = ILE->getInit(ElementNo++); + if (Init && isa(Init)) + continue; + + // Zero-sized fields are not emitted, but their initializers may still + // prevent emission of this struct as a constant. + if (Field->isZeroSize(CGM.getASTContext())) { + if (Init->HasSideEffects(CGM.getASTContext())) + return false; + continue; + } + + // When emitting a DesignatedInitUpdateExpr, a nested InitListExpr + // represents additional overwriting of our current constant value, and not + // a new constant to emit independently. + if (AllowOverwrite && + (Field->getType()->isArrayType() || Field->getType()->isRecordType())) { + if (auto *SubILE = dyn_cast(Init)) { + CharUnits Offset = CGM.getASTContext().toCharUnitsFromBits( + Layout.getFieldOffset(FieldNo)); + if (!EmitDesignatedInitUpdater(Emitter, Builder, StartOffset + Offset, + Field->getType(), SubILE)) + return false; + // If we split apart the field's value, try to collapse it down to a + // single value now. + llvm_unreachable("NYI"); + continue; + } + } + + mlir::Attribute EltInit; + if (Init) + EltInit = Emitter.tryEmitPrivateForMemory(Init, Field->getType()); + else + llvm_unreachable("NYI"); + + if (!EltInit) + return false; + + if (!Field->isBitField()) { + // Handle non-bitfield members. + if (!AppendField(Field, Layout.getFieldOffset(FieldNo), EltInit, + AllowOverwrite)) + return false; + // After emitting a non-empty field with [[no_unique_address]], we may + // need to overwrite its tail padding. + if (Field->hasAttr()) + AllowOverwrite = true; + } else { + llvm_unreachable("NYI"); + } + } + + return true; +} + +namespace { +struct BaseInfo { + BaseInfo(const CXXRecordDecl *Decl, CharUnits Offset, unsigned Index) + : Decl(Decl), Offset(Offset), Index(Index) {} + + const CXXRecordDecl *Decl; + CharUnits Offset; + unsigned Index; + + bool operator<(const BaseInfo &O) const { return Offset < O.Offset; } +}; +} // namespace + +bool ConstStructBuilder::Build(const APValue &Val, const RecordDecl *RD, + bool IsPrimaryBase, + const CXXRecordDecl *VTableClass, + CharUnits Offset) { + const ASTRecordLayout &Layout = CGM.getASTContext().getASTRecordLayout(RD); + + if (const CXXRecordDecl *CD = dyn_cast(RD)) { + // Add a vtable pointer, if we need one and it hasn't already been added. + if (Layout.hasOwnVFPtr()) + llvm_unreachable("NYI"); + + // Accumulate and sort bases, in order to visit them in address order, which + // may not be the same as declaration order. + SmallVector Bases; + Bases.reserve(CD->getNumBases()); + unsigned BaseNo = 0; + for (CXXRecordDecl::base_class_const_iterator Base = CD->bases_begin(), + BaseEnd = CD->bases_end(); + Base != BaseEnd; ++Base, ++BaseNo) { + assert(!Base->isVirtual() && "should not have virtual bases here"); + const CXXRecordDecl *BD = Base->getType()->getAsCXXRecordDecl(); + CharUnits BaseOffset = Layout.getBaseClassOffset(BD); + Bases.push_back(BaseInfo(BD, BaseOffset, BaseNo)); + } + llvm::stable_sort(Bases); + + for (unsigned I = 0, N = Bases.size(); I != N; ++I) { + BaseInfo &Base = Bases[I]; + + bool IsPrimaryBase = Layout.getPrimaryBase() == Base.Decl; + Build(Val.getStructBase(Base.Index), Base.Decl, IsPrimaryBase, + VTableClass, Offset + Base.Offset); + } + } + + unsigned FieldNo = 0; + uint64_t OffsetBits = CGM.getASTContext().toBits(Offset); + + bool AllowOverwrite = false; + for (RecordDecl::field_iterator Field = RD->field_begin(), + FieldEnd = RD->field_end(); + Field != FieldEnd; ++Field, ++FieldNo) { + // If this is a union, skip all the fields that aren't being initialized. + if (RD->isUnion() && !declaresSameEntity(Val.getUnionField(), *Field)) + continue; + + // Don't emit anonymous bitfields or zero-sized fields. + if (Field->isUnnamedBitfield() || Field->isZeroSize(CGM.getASTContext())) + continue; + + // Emit the value of the initializer. + const APValue &FieldValue = + RD->isUnion() ? Val.getUnionValue() : Val.getStructField(FieldNo); + mlir::Attribute EltInit = + Emitter.tryEmitPrivateForMemory(FieldValue, Field->getType()); + if (!EltInit) + return false; + + if (!Field->isBitField()) { + // Handle non-bitfield members. + if (!AppendField(*Field, Layout.getFieldOffset(FieldNo) + OffsetBits, + EltInit, AllowOverwrite)) + return false; + // After emitting a non-empty field with [[no_unique_address]], we may + // need to overwrite its tail padding. + if (Field->hasAttr()) + AllowOverwrite = true; + } else { + llvm_unreachable("NYI"); + } + } + + return true; +} + +mlir::Attribute ConstStructBuilder::Finalize(QualType Type) { + Type = Type.getNonReferenceType(); + RecordDecl *RD = Type->castAs()->getDecl(); + mlir::Type ValTy = CGM.getTypes().ConvertType(Type); + return Builder.build(ValTy, RD->hasFlexibleArrayMember()); +} + +mlir::Attribute ConstStructBuilder::BuildStruct(ConstantEmitter &Emitter, + InitListExpr *ILE, + QualType ValTy) { + ConstantAggregateBuilder Const(Emitter.CGM); + ConstStructBuilder Builder(Emitter, Const, CharUnits::Zero()); + + if (!Builder.Build(ILE, /*AllowOverwrite*/ false)) + return nullptr; + + return Builder.Finalize(ValTy); +} + +mlir::Attribute ConstStructBuilder::BuildStruct(ConstantEmitter &Emitter, + const APValue &Val, + QualType ValTy) { + ConstantAggregateBuilder Const(Emitter.CGM); + ConstStructBuilder Builder(Emitter, Const, CharUnits::Zero()); + + const RecordDecl *RD = ValTy->castAs()->getDecl(); + const CXXRecordDecl *CD = dyn_cast(RD); + if (!Builder.Build(Val, RD, false, CD, CharUnits::Zero())) + return nullptr; + + return Builder.Finalize(ValTy); +} + +bool ConstStructBuilder::UpdateStruct(ConstantEmitter &Emitter, + ConstantAggregateBuilder &Const, + CharUnits Offset, InitListExpr *Updater) { + return ConstStructBuilder(Emitter, Const, Offset) + .Build(Updater, /*AllowOverwrite*/ true); +} + +//===----------------------------------------------------------------------===// +// 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 { + 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 nullptr; } + + mlir::Attribute VisitConstantExpr(ConstantExpr *CE, QualType T) { + assert(0 && "unimplemented"); + return {}; + } + + 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) { + if (const auto *ECE = dyn_cast(E)) + llvm_unreachable("NYI"); + Expr *subExpr = E->getSubExpr(); + + switch (E->getCastKind()) { + case CK_ToUnion: { + llvm_unreachable("not implemented"); + } + + case CK_AddressSpaceConversion: { + llvm_unreachable("not implemented"); + } + + case CK_LValueToRValue: + case CK_AtomicToNonAtomic: + case CK_NonAtomicToAtomic: + case CK_NoOp: + case CK_ConstructorConversion: + return Visit(subExpr, destType); + + case CK_IntToOCLSampler: + llvm_unreachable("global sampler variables are not generated"); + + case CK_Dependent: + llvm_unreachable("saw dependent cast!"); + + case CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + + case CK_ReinterpretMemberPointer: + case CK_DerivedToBaseMemberPointer: + case CK_BaseToDerivedMemberPointer: { + llvm_unreachable("not implemented"); + } + + // These will never be supported. + case CK_ObjCObjectLValueCast: + case CK_ARCProduceObject: + case CK_ARCConsumeObject: + case CK_ARCReclaimReturnedObject: + case CK_ARCExtendBlockObject: + case CK_CopyAndAutoreleaseBlockObject: + return nullptr; + + // These don't need to be handled here because Evaluate knows how to + // evaluate them in the cases where they can be folded. + case CK_BitCast: + case CK_ToVoid: + case CK_Dynamic: + case CK_LValueBitCast: + case CK_LValueToRValueBitCast: + case CK_NullToMemberPointer: + case CK_UserDefinedConversion: + case CK_CPointerToObjCPointerCast: + case CK_BlockPointerToObjCPointerCast: + case CK_AnyPointerToBlockPointerCast: + case CK_ArrayToPointerDecay: + case CK_FunctionToPointerDecay: + case CK_BaseToDerived: + case CK_DerivedToBase: + case CK_UncheckedDerivedToBase: + case CK_MemberPointerToBoolean: + case CK_VectorSplat: + case CK_FloatingRealToComplex: + case CK_FloatingComplexToReal: + case CK_FloatingComplexToBoolean: + case CK_FloatingComplexCast: + case CK_FloatingComplexToIntegralComplex: + case CK_IntegralRealToComplex: + case CK_IntegralComplexToReal: + case CK_IntegralComplexToBoolean: + case CK_IntegralComplexCast: + case CK_IntegralComplexToFloatingComplex: + case CK_PointerToIntegral: + case CK_PointerToBoolean: + case CK_NullToPointer: + case CK_IntegralCast: + case CK_BooleanToSignedIntegral: + case CK_IntegralToPointer: + case CK_IntegralToBoolean: + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + case CK_FloatingToBoolean: + case CK_FloatingCast: + case CK_FloatingToFixedPoint: + case CK_FixedPointToFloating: + case CK_FixedPointCast: + case CK_FixedPointToBoolean: + case CK_FixedPointToIntegral: + case CK_IntegralToFixedPoint: + case CK_ZeroToOCLOpaqueType: + case CK_MatrixCast: + return nullptr; + } + llvm_unreachable("Invalid CastKind"); + } + + mlir::Attribute VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DIE, QualType T) { + // TODO(cir): figure out CIR story here... + // No need for a DefaultInitExprScope: we don't handle 'this' in a + // constant expression. + return Visit(DIE->getExpr(), T); + } + + 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 EmitArrayInitialization(InitListExpr *ILE, QualType T) { + auto *CAT = CGM.getASTContext().getAsConstantArrayType(ILE->getType()); + assert(CAT && "can't emit array init for non-constant-bound array"); + unsigned NumInitElements = ILE->getNumInits(); // init list size + unsigned NumElements = CAT->getSize().getZExtValue(); // array size + unsigned NumInitableElts = std::min(NumInitElements, NumElements); + + QualType EltTy = CAT->getElementType(); + SmallVector Elts; + Elts.reserve(NumElements); + + // Emit array filler, if there is one. + mlir::Attribute Filler; + if (ILE->hasArrayFiller()) { + auto *aux = ILE->getArrayFiller(); + Filler = Emitter.tryEmitAbstractForMemory(aux, CAT->getElementType()); + if (!Filler) + return {}; + } + + // Emit initializer elements as MLIR attributes and check for common type. + mlir::Type CommonElementType; + for (unsigned i = 0; i != NumInitableElts; ++i) { + Expr *Init = ILE->getInit(i); + auto C = Emitter.tryEmitPrivateForMemory(Init, EltTy); + if (!C) + return {}; + if (i == 0) + CommonElementType = C.getType(); + else if (C.getType() != CommonElementType) + CommonElementType = nullptr; + Elts.push_back(std::move(C)); + } + + auto desiredType = CGM.getTypes().ConvertType(T); + auto typedFiller = llvm::dyn_cast_or_null(Filler); + if (Filler && !typedFiller) + llvm_unreachable("We shouldn't be receiving untyped attrs here"); + return buildArrayConstant(CGM, desiredType, CommonElementType, NumElements, + Elts, typedFiller); + } + + mlir::Attribute EmitRecordInitialization(InitListExpr *ILE, QualType T) { + return ConstStructBuilder::BuildStruct(Emitter, ILE, T); + } + + mlir::Attribute VisitImplicitValueInitExpr(ImplicitValueInitExpr *E, + QualType T) { + return CGM.getBuilder().getZeroInitAttr(CGM.getCIRType(T)); + } + + mlir::Attribute VisitInitListExpr(InitListExpr *ILE, QualType T) { + if (ILE->isTransparent()) + return Visit(ILE->getInit(0), T); + + if (ILE->getType()->isArrayType()) + return EmitArrayInitialization(ILE, T); + + if (ILE->getType()->isRecordType()) + return EmitRecordInitialization(ILE, T); + + return nullptr; + } + + mlir::Attribute VisitDesignatedInitUpdateExpr(DesignatedInitUpdateExpr *E, + QualType destType) { + auto C = Visit(E->getBase(), destType); + if (!C) + return nullptr; + + assert(0 && "not implemented"); + return {}; + } + + mlir::Attribute VisitCXXConstructExpr(CXXConstructExpr *E, QualType Ty) { + if (!E->getConstructor()->isTrivial()) + return nullptr; + + // Only default and copy/move constructors can be trivial. + if (E->getNumArgs()) { + assert(E->getNumArgs() == 1 && "trivial ctor with > 1 argument"); + assert(E->getConstructor()->isCopyOrMoveConstructor() && + "trivial ctor has argument but isn't a copy/move ctor"); + + Expr *Arg = E->getArg(0); + assert(CGM.getASTContext().hasSameUnqualifiedType(Ty, Arg->getType()) && + "argument to copy ctor is of wrong type"); + + // Look through the temporary; it's just converting the value to an lvalue + // to pass it to the constructor. + if (auto *MTE = dyn_cast(Arg)) + return Visit(MTE->getSubExpr(), Ty); + // Don't try to support arbitrary lvalue-to-rvalue conversions for now. + return nullptr; + } + + llvm_unreachable("NYI"); + } + + mlir::Attribute VisitStringLiteral(StringLiteral *E, QualType T) { + // This is a string literal initializing an array in an initializer. + return CGM.getConstantArrayFromStringLiteral(E); + } + + mlir::Attribute VisitObjCEncodeExpr(ObjCEncodeExpr *E, QualType T) { + assert(0 && "not implemented"); + return {}; + } + + mlir::Attribute VisitUnaryExtension(const UnaryOperator *E, QualType T) { + return Visit(E->getSubExpr(), T); + } + + // Utility methods + mlir::Type ConvertType(QualType T) { return CGM.getTypes().ConvertType(T); } +}; + +static mlir::Attribute +buildArrayConstant(CIRGenModule &CGM, mlir::Type DesiredType, + mlir::Type CommonElementType, unsigned ArrayBound, + SmallVectorImpl &Elements, + mlir::TypedAttr Filler) { + auto &builder = CGM.getBuilder(); + + // Figure out how long the initial prefix of non-zero elements is. + unsigned NonzeroLength = ArrayBound; + if (Elements.size() < NonzeroLength && builder.isNullValue(Filler)) + NonzeroLength = Elements.size(); + if (NonzeroLength == Elements.size()) { + while (NonzeroLength > 0 && + builder.isNullValue(Elements[NonzeroLength - 1])) + --NonzeroLength; + } + + if (NonzeroLength == 0) + return builder.getZeroInitAttr(DesiredType); + + // Add a zeroinitializer array filler if we have lots of trailing zeroes. + unsigned TrailingZeroes = ArrayBound - NonzeroLength; + if (TrailingZeroes >= 8) { + assert(0 && "NYE"); + assert(Elements.size() >= NonzeroLength && + "missing initializer for non-zero element"); + + // TODO(cir): If all the elements had the same type up to the trailing + // zeroes, emit a struct of two arrays (the nonzero data and the + // zeroinitializer). Use DesiredType to get the element type. + } else if (Elements.size() != ArrayBound) { + // Otherwise pad to the right size with the filler if necessary. + Elements.resize(ArrayBound, Filler); + if (Filler.getType() != CommonElementType) + CommonElementType = {}; + } + + // If all elements have the same type, just emit an array constant. + if (CommonElementType) { + SmallVector Eles; + Eles.reserve(Elements.size()); + for (auto const &Element : Elements) + Eles.push_back(Element); + + return builder.getConstArray( + mlir::ArrayAttr::get(builder.getContext(), Eles), + mlir::cir::ArrayType::get(builder.getContext(), CommonElementType, + ArrayBound)); + } + + // We have mixed types. Use a packed struct. + assert(0 && "NYE"); + return {}; +} + +} // end anonymous namespace. + +//===----------------------------------------------------------------------===// +// ConstantLValueEmitter +//===----------------------------------------------------------------------===// + +namespace { +/// A struct which can be used to peephole certain kinds of finalization +/// that normally happen during l-value emission. +struct ConstantLValue { + llvm::PointerUnion Value; + bool HasOffsetApplied; + + /*implicit*/ ConstantLValue(mlir::Value value, bool hasOffsetApplied = false) + : Value(value), HasOffsetApplied(hasOffsetApplied) {} + + /*implicit*/ ConstantLValue(mlir::cir::GlobalViewAttr address) : Value(address) {} + + ConstantLValue(std::nullptr_t) : ConstantLValue({}, false) {} + ConstantLValue(mlir::Attribute value) : Value(value) {} +}; + +/// A helper class for emitting constant l-values. +class ConstantLValueEmitter + : public ConstStmtVisitor { + CIRGenModule &CGM; + ConstantEmitter &Emitter; + const APValue &Value; + QualType DestType; + + // Befriend StmtVisitorBase so that we don't have to expose Visit*. + friend StmtVisitorBase; + +public: + ConstantLValueEmitter(ConstantEmitter &emitter, const APValue &value, + QualType destType) + : CGM(emitter.CGM), Emitter(emitter), Value(value), DestType(destType) {} + + mlir::Attribute tryEmit(); + +private: + mlir::Attribute tryEmitAbsolute(mlir::Type destTy); + ConstantLValue tryEmitBase(const APValue::LValueBase &base); + + ConstantLValue VisitStmt(const Stmt *S) { return nullptr; } + ConstantLValue VisitConstantExpr(const ConstantExpr *E); + ConstantLValue VisitCompoundLiteralExpr(const CompoundLiteralExpr *E); + ConstantLValue VisitStringLiteral(const StringLiteral *E); + ConstantLValue VisitObjCBoxedExpr(const ObjCBoxedExpr *E); + ConstantLValue VisitObjCEncodeExpr(const ObjCEncodeExpr *E); + ConstantLValue VisitObjCStringLiteral(const ObjCStringLiteral *E); + ConstantLValue VisitPredefinedExpr(const PredefinedExpr *E); + ConstantLValue VisitAddrLabelExpr(const AddrLabelExpr *E); + ConstantLValue VisitCallExpr(const CallExpr *E); + ConstantLValue VisitBlockExpr(const BlockExpr *E); + ConstantLValue VisitCXXTypeidExpr(const CXXTypeidExpr *E); + ConstantLValue + VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E); + + bool hasNonZeroOffset() const { return !Value.getLValueOffset().isZero(); } + + /// Return the value offset. + mlir::Attribute getOffset() { llvm_unreachable("NYI"); } + + // TODO(cir): create a proper interface to absctract CIR constant values. + + /// Apply the value offset to the given constant. + ConstantLValue applyOffset(ConstantLValue &C) { + if (!hasNonZeroOffset()) + return C; + + // TODO(cir): use ptr_stride, or something... + llvm_unreachable("NYI"); + } +}; + +} // namespace + +mlir::Attribute ConstantLValueEmitter::tryEmit() { + const APValue::LValueBase &base = Value.getLValueBase(); + + // The destination type should be a pointer or reference + // type, but it might also be a cast thereof. + // + // FIXME: the chain of casts required should be reflected in the APValue. + // We need this in order to correctly handle things like a ptrtoint of a + // non-zero null pointer and addrspace casts that aren't trivially + // represented in LLVM IR. + auto destTy = CGM.getTypes().convertTypeForMem(DestType); + assert(destTy.isa()); + + // If there's no base at all, this is a null or absolute pointer, + // possibly cast back to an integer type. + if (!base) { + return tryEmitAbsolute(destTy); + } + + // Otherwise, try to emit the base. + ConstantLValue result = tryEmitBase(base); + + // If that failed, we're done. + auto &value = result.Value; + if (!value) + return {}; + + // Apply the offset if necessary and not already done. + if (!result.HasOffsetApplied && !value.is()) { + value = applyOffset(result).Value; + } + + // Convert to the appropriate type; this could be an lvalue for + // an integer. FIXME: performAddrSpaceCast + if (destTy.isa()) { + if (value.is()) + return value.get(); + llvm_unreachable("NYI"); + } + + llvm_unreachable("NYI"); +} + +/// Try to emit an absolute l-value, such as a null pointer or an integer +/// bitcast to pointer type. +mlir::Attribute ConstantLValueEmitter::tryEmitAbsolute(mlir::Type destTy) { + // If we're producing a pointer, this is easy. + auto destPtrTy = destTy.dyn_cast(); + assert(destPtrTy && "expected !cir.ptr type"); + return CGM.getBuilder().getConstPtrAttr( + destPtrTy, Value.getLValueOffset().getQuantity()); +} + +ConstantLValue +ConstantLValueEmitter::tryEmitBase(const APValue::LValueBase &base) { + // Handle values. + if (const ValueDecl *D = base.dyn_cast()) { + // The constant always points to the canonical declaration. We want to look + // at properties of the most recent declaration at the point of emission. + D = cast(D->getMostRecentDecl()); + + if (D->hasAttr()) + llvm_unreachable("emit pointer base for weakref is NYI"); + + if (auto *FD = dyn_cast(D)) + llvm_unreachable("emit pointer base for fun decl is NYI"); + + if (auto *VD = dyn_cast(D)) { + // We can never refer to a variable with local storage. + if (!VD->hasLocalStorage()) { + if (VD->isFileVarDecl() || VD->hasExternalStorage()) + return CGM.getAddrOfGlobalVarAttr(VD); + + if (VD->isLocalVarDecl()) { + auto linkage = + CGM.getCIRLinkageVarDefinition(VD, /*IsConstant=*/false); + return CGM.getBuilder().getGlobalViewAttr( + CGM.getOrCreateStaticVarDecl(*VD, linkage)); + } + } + } + } + + // Handle typeid(T). + if (TypeInfoLValue TI = base.dyn_cast()) { + assert(0 && "NYI"); + } + + // Otherwise, it must be an expression. + return Visit(base.get()); +} + +ConstantLValue ConstantLValueEmitter::VisitConstantExpr(const ConstantExpr *E) { + assert(0 && "NYI"); + return Visit(E->getSubExpr()); +} + +ConstantLValue +ConstantLValueEmitter::VisitCompoundLiteralExpr(const CompoundLiteralExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue +ConstantLValueEmitter::VisitStringLiteral(const StringLiteral *E) { + return CGM.getAddrOfConstantStringFromLiteral(E); +} + +ConstantLValue +ConstantLValueEmitter::VisitObjCEncodeExpr(const ObjCEncodeExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue +ConstantLValueEmitter::VisitObjCStringLiteral(const ObjCStringLiteral *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue +ConstantLValueEmitter::VisitObjCBoxedExpr(const ObjCBoxedExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue +ConstantLValueEmitter::VisitPredefinedExpr(const PredefinedExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue +ConstantLValueEmitter::VisitAddrLabelExpr(const AddrLabelExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue ConstantLValueEmitter::VisitCallExpr(const CallExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue ConstantLValueEmitter::VisitBlockExpr(const BlockExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue +ConstantLValueEmitter::VisitCXXTypeidExpr(const CXXTypeidExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +ConstantLValue ConstantLValueEmitter::VisitMaterializeTemporaryExpr( + const MaterializeTemporaryExpr *E) { + assert(0 && "NYI"); + return nullptr; +} + +//===----------------------------------------------------------------------===// +// ConstantEmitter +//===----------------------------------------------------------------------===// + +mlir::Attribute ConstantEmitter::validateAndPopAbstract(mlir::Attribute C, + AbstractState saved) { + Abstract = saved.OldValue; + + assert(saved.OldPlaceholdersSize == PlaceholderAddresses.size() && + "created a placeholder while doing an abstract emission?"); + + // No validation necessary for now. + // No cleanup to do for now. + return C; +} + +mlir::Attribute ConstantEmitter::tryEmitForInitializer(const VarDecl &D) { + initializeNonAbstract(D.getType().getAddressSpace()); + return markIfFailed(tryEmitPrivateForVarInit(D)); +} + +void ConstantEmitter::finalize(mlir::cir::GlobalOp global) { + assert(InitializedNonAbstract && + "finalizing emitter that was used for abstract emission?"); + assert(!Finalized && "finalizing emitter multiple times"); + assert(!global.isDeclaration()); + + // Note that we might also be Failed. + Finalized = true; + + if (!PlaceholderAddresses.empty()) { + assert(0 && "not implemented"); + } +} + +ConstantEmitter::~ConstantEmitter() { + assert((!InitializedNonAbstract || Finalized || Failed) && + "not finalized after being initialized for non-abstract emission"); + assert(PlaceholderAddresses.empty() && "unhandled placeholders"); +} + +// TODO(cir): this can be shared with LLVM's codegen +static QualType getNonMemoryType(CIRGenModule &CGM, QualType type) { + if (auto AT = type->getAs()) { + return CGM.getASTContext().getQualifiedType(AT->getValueType(), + type.getQualifiers()); + } + return type; +} + +mlir::Attribute +ConstantEmitter::tryEmitAbstractForInitializer(const VarDecl &D) { + auto state = pushAbstract(); + auto 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 (const CXXConstructExpr *E = + dyn_cast_or_null(D.getInit())) { + const CXXConstructorDecl *CD = E->getConstructor(); + // FIXME: we should probably model this more closely to C++ than + // just emitting a global with zero init (mimic what we do for trivial + // assignments and whatnots). Since this is for globals shouldn't + // be a problem for the near future. + if (CD->isTrivial() && CD->isDefaultConstructor()) + return mlir::cir::ZeroAttr::get( + CGM.getBuilder().getContext(), + CGM.getTypes().ConvertType(D.getType())); + } + } + 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 (auto C = ConstExprEmitter(*this).Visit(const_cast(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 (auto value = D.evaluateValue()) + return tryEmitPrivateForMemory(*value, destType); + + return nullptr; +} + +mlir::Attribute ConstantEmitter::tryEmitAbstract(const Expr *E, + QualType destType) { + auto state = pushAbstract(); + auto C = tryEmitPrivate(E, destType); + return validateAndPopAbstract(C, state); +} + +mlir::Attribute ConstantEmitter::tryEmitAbstract(const APValue &value, + QualType destType) { + auto state = pushAbstract(); + auto C = tryEmitPrivate(value, destType); + return validateAndPopAbstract(C, state); +} + +mlir::Attribute ConstantEmitter::tryEmitAbstractForMemory(const Expr *E, + QualType destType) { + auto nonMemoryDestType = getNonMemoryType(CGM, destType); + auto C = tryEmitAbstract(E, nonMemoryDestType); + return (C ? emitForMemory(C, destType) : nullptr); +} + +mlir::Attribute ConstantEmitter::tryEmitAbstractForMemory(const APValue &value, + QualType destType) { + auto nonMemoryDestType = getNonMemoryType(CGM, destType); + auto C = tryEmitAbstract(value, nonMemoryDestType); + return (C ? emitForMemory(C, destType) : nullptr); +} + +mlir::TypedAttr ConstantEmitter::tryEmitPrivateForMemory(const Expr *E, + QualType destType) { + auto nonMemoryDestType = getNonMemoryType(CGM, destType); + auto C = tryEmitPrivate(E, nonMemoryDestType); + if (C) { + auto attr = emitForMemory(C, destType); + auto typedAttr = llvm::dyn_cast(attr); + if (!typedAttr) + llvm_unreachable("this should always be typed"); + return typedAttr; + } else { + return nullptr; + } +} + +mlir::Attribute ConstantEmitter::tryEmitPrivateForMemory(const APValue &value, + QualType destType) { + auto nonMemoryDestType = getNonMemoryType(CGM, destType); + auto C = tryEmitPrivate(value, nonMemoryDestType); + return (C ? emitForMemory(C, destType) : nullptr); +} + +mlir::Attribute ConstantEmitter::emitForMemory(CIRGenModule &CGM, + mlir::Attribute C, + QualType destType) { + // For an _Atomic-qualified constant, we may need to add tail padding. + if (auto AT = destType->getAs()) { + assert(0 && "not implemented"); + } + + // Zero-extend bool. + auto typed = C.dyn_cast(); + if (typed && typed.getType().isa()) { + // Already taken care given that bool values coming from + // integers only carry true/false. + } + + return C; +} + +mlir::TypedAttr ConstantEmitter::tryEmitPrivate(const Expr *E, + QualType destType) { + assert(!destType->isVoidType() && "can't emit a void constant"); + + if (auto C = ConstExprEmitter(*this).Visit(const_cast(E), destType)) { + if (auto TypedC = C.dyn_cast_or_null()) + return TypedC; + llvm_unreachable("this should always be typed"); + } + + Expr::EvalResult Result; + + bool Success; + + if (destType->isReferenceType()) + Success = E->EvaluateAsLValue(Result, CGM.getASTContext()); + else + Success = + E->EvaluateAsRValue(Result, CGM.getASTContext(), InConstantContext); + + if (Success && !Result.hasSideEffects()) { + auto C = tryEmitPrivate(Result.Val, destType); + if (auto TypedC = C.dyn_cast_or_null()) + return TypedC; + llvm_unreachable("this should always be typed"); + } + + return nullptr; +} + +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. + assert(0 && "not implemented"); + case APValue::Int: { + mlir::Type ty = CGM.getCIRType(DestType); + if (ty.isa()) + return builder.getCIRBoolAttr(Value.getInt().getZExtValue()); + assert(ty.isa() && "expected integral type"); + return CGM.getBuilder().getAttr(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()) + assert(0 && "not implemented"); + else { + mlir::Type ty = CGM.getCIRType(DestType); + return builder.getFloatAttr(ty, Init); + } + } + case APValue::Array: { + const ArrayType *ArrayTy = CGM.getASTContext().getAsArrayType(DestType); + unsigned NumElements = Value.getArraySize(); + unsigned NumInitElts = Value.getArrayInitializedElts(); + + // Emit array filler, if there is one. + mlir::Attribute Filler; + if (Value.hasArrayFiller()) { + Filler = tryEmitAbstractForMemory(Value.getArrayFiller(), + ArrayTy->getElementType()); + if (!Filler) + return {}; + } + + // Emit initializer elements. + SmallVector Elts; + if (Filler && builder.isNullValue(Filler)) + Elts.reserve(NumInitElts + 1); + else + Elts.reserve(NumElements); + + mlir::Type CommonElementType; + for (unsigned I = 0; I < NumInitElts; ++I) { + auto C = tryEmitPrivateForMemory(Value.getArrayInitializedElt(I), + ArrayTy->getElementType()); + if (!C) + return {}; + + assert(C.isa() && "This should always be a TypedAttr."); + auto CTyped = C.cast(); + + if (I == 0) + CommonElementType = CTyped.getType(); + else if (CTyped.getType() != CommonElementType) + CommonElementType = {}; + auto typedC = llvm::dyn_cast(C); + if (!typedC) + llvm_unreachable("this should always be typed"); + Elts.push_back(typedC); + } + + auto Desired = CGM.getTypes().ConvertType(DestType); + + auto typedFiller = llvm::dyn_cast_or_null(Filler); + if (Filler && !typedFiller) + llvm_unreachable("this should always be typed"); + + return buildArrayConstant(CGM, Desired, CommonElementType, NumElements, + Elts, typedFiller); + } + case APValue::LValue: + return ConstantLValueEmitter(*this, Value, DestType).tryEmit(); + case APValue::Struct: + case APValue::Union: + return ConstStructBuilder::BuildStruct(*this, Value, DestType); + case APValue::FixedPoint: + case APValue::ComplexInt: + case APValue::ComplexFloat: + case APValue::Vector: + case APValue::AddrLabelDiff: + case APValue::MemberPointer: + assert(0 && "not implemented"); + } + llvm_unreachable("Unknown APValue kind"); +} + +mlir::Value CIRGenModule::buildNullConstant(QualType T, mlir::Location loc) { + if (T->getAs()) { + return builder.getNullPtr(getTypes().convertTypeForMem(T), loc); + } + + if (getTypes().isZeroInitializable(T)) + return builder.getNullValue(getTypes().convertTypeForMem(T), loc); + + if (const ConstantArrayType *CAT = + getASTContext().getAsConstantArrayType(T)) { + llvm_unreachable("NYI"); + } + + if (const RecordType *RT = T->getAs()) + llvm_unreachable("NYI"); + + assert(T->isMemberDataPointerType() && + "Should only see pointers to data members here!"); + + llvm_unreachable("NYI"); + return {}; +} + +mlir::Attribute ConstantEmitter::emitAbstract(const Expr *E, + QualType destType) { + auto state = pushAbstract(); + auto C = tryEmitPrivate(E, destType).cast(); + C = validateAndPopAbstract(C, state); + if (!C) { + llvm_unreachable("NYI"); + } + return C; +} + +mlir::Attribute ConstantEmitter::emitAbstract(SourceLocation loc, + const APValue &value, + QualType destType) { + auto state = pushAbstract(); + auto C = tryEmitPrivate(value, destType); + C = validateAndPopAbstract(C, state); + if (!C) { + CGM.Error(loc, + "internal error: could not emit constant value \"abstractly\""); + llvm_unreachable("NYI"); + } + return C; +} diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp new file mode 100644 index 000000000000..f756ff1e0224 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -0,0 +1,2300 @@ +//===--- CIRGenExprScalar.cpp - Emit CIR Code for Scalar Exprs ------------===// +// +// 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 with scalar CIR types as CIR code. +// +//===----------------------------------------------------------------------===// + +#include "CIRDataLayout.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/StmtVisitor.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/Support/ErrorHandling.h" +#include + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Value.h" + +using namespace cir; +using namespace clang; + +namespace { + +struct BinOpInfo { + mlir::Value LHS; + mlir::Value RHS; + SourceRange Loc; + QualType Ty; // Computation Type. + BinaryOperator::Opcode Opcode; // Opcode of BinOp to perform + FPOptions FPFeatures; + const Expr *E; // Entire expr, for error unsupported. May not be binop. + + /// Check if the binop computes a division or a remainder. + bool isDivremOp() const { + return Opcode == BO_Div || Opcode == BO_Rem || Opcode == BO_DivAssign || + Opcode == BO_RemAssign; + } + + /// Check if the binop can result in integer overflow. + bool mayHaveIntegerOverflow() const { + // Without constant input, we can't rule out overflow. + auto LHSCI = dyn_cast(LHS.getDefiningOp()); + auto RHSCI = dyn_cast(RHS.getDefiningOp()); + if (!LHSCI || !RHSCI) + return true; + + llvm::APInt Result; + assert(!UnimplementedFeature::mayHaveIntegerOverflow()); + llvm_unreachable("NYI"); + return false; + } + + /// Check if at least one operand is a fixed point type. In such cases, + /// this operation did not follow usual arithmetic conversion and both + /// operands might not be of the same type. + bool isFixedPointOp() const { + // We cannot simply check the result type since comparison operations + // return an int. + if (const auto *BinOp = llvm::dyn_cast(E)) { + QualType LHSType = BinOp->getLHS()->getType(); + QualType RHSType = BinOp->getRHS()->getType(); + return LHSType->isFixedPointType() || RHSType->isFixedPointType(); + } + if (const auto *UnOp = llvm::dyn_cast(E)) + return UnOp->getSubExpr()->getType()->isFixedPointType(); + return false; + } +}; + +static bool PromotionIsPotentiallyEligibleForImplicitIntegerConversionCheck( + QualType SrcType, QualType DstType) { + return SrcType->isIntegerType() && DstType->isIntegerType(); +} + +class ScalarExprEmitter : public StmtVisitor { + CIRGenFunction &CGF; + CIRGenBuilderTy &Builder; + bool IgnoreResultAssign; + +public: + ScalarExprEmitter(CIRGenFunction &cgf, CIRGenBuilderTy &builder, + bool ira = false) + : CGF(cgf), Builder(builder), IgnoreResultAssign(ira) {} + + //===--------------------------------------------------------------------===// + // Utilities + //===--------------------------------------------------------------------===// + + bool TestAndClearIgnoreResultAssign() { + bool I = IgnoreResultAssign; + IgnoreResultAssign = false; + return I; + } + + mlir::Type ConvertType(QualType T) { return CGF.ConvertType(T); } + LValue buildLValue(const Expr *E) { return CGF.buildLValue(E); } + LValue buildCheckedLValue(const Expr *E, CIRGenFunction::TypeCheckKind TCK) { + return CGF.buildCheckedLValue(E, TCK); + } + + /// Emit a value that corresponds to null for the given type. + mlir::Value buildNullValue(QualType Ty, mlir::Location loc); + + //===--------------------------------------------------------------------===// + // Visitor Methods + //===--------------------------------------------------------------------===// + + mlir::Value Visit(Expr *E) { + return StmtVisitor::Visit(E); + } + + mlir::Value VisitStmt(Stmt *S) { + S->dump(llvm::errs(), CGF.getContext()); + llvm_unreachable("Stmt can't have complex result type!"); + } + + mlir::Value VisitExpr(Expr *E) { + // Crashing here for "ScalarExprClassName"? Please implement + // VisitScalarExprClassName(...) to get this working. + emitError(CGF.getLoc(E->getExprLoc()), "scalar exp no implemented: '") + << E->getStmtClassName() << "'"; + assert(0 && "shouldn't be here!"); + return {}; + } + + mlir::Value VisitConstantExpr(ConstantExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitParenExpr(ParenExpr *PE) { return Visit(PE->getSubExpr()); } + mlir::Value + VisitSubstnonTypeTemplateParmExpr(SubstNonTypeTemplateParmExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitGenericSelectionExpr(GenericSelectionExpr *GE) { + llvm_unreachable("NYI"); + } + mlir::Value VisitCoawaitExpr(CoawaitExpr *S) { + return CGF.buildCoawaitExpr(*S).getScalarVal(); + } + mlir::Value VisitCoyieldExpr(CoyieldExpr *S) { llvm_unreachable("NYI"); } + mlir::Value VisitUnaryCoawait(const UnaryOperator *E) { + llvm_unreachable("NYI"); + } + + // Leaves. + mlir::Value VisitIntegerLiteral(const IntegerLiteral *E) { + mlir::Type Ty = CGF.getCIRType(E->getType()); + return Builder.create( + CGF.getLoc(E->getExprLoc()), Ty, + Builder.getAttr(Ty, E->getValue())); + } + + mlir::Value VisitFixedPointLiteral(const FixedPointLiteral *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitFloatingLiteral(const FloatingLiteral *E) { + mlir::Type Ty = CGF.getCIRType(E->getType()); + return Builder.create( + CGF.getLoc(E->getExprLoc()), Ty, + Builder.getFloatAttr(Ty, E->getValue())); + } + mlir::Value VisitCharacterLiteral(const CharacterLiteral *E) { + mlir::Type Ty = CGF.getCIRType(E->getType()); + auto loc = CGF.getLoc(E->getExprLoc()); + auto init = mlir::cir::IntAttr::get(Ty, E->getValue()); + return Builder.create(loc, Ty, init); + } + mlir::Value VisitObjCBoolLiteralExpr(const ObjCBoolLiteralExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitCXXBoolLiteralExpr(const CXXBoolLiteralExpr *E) { + mlir::Type Ty = CGF.getCIRType(E->getType()); + return Builder.create( + CGF.getLoc(E->getExprLoc()), Ty, Builder.getCIRBoolAttr(E->getValue())); + } + + mlir::Value VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *E) { + if (E->getType()->isVoidType()) + return nullptr; + + return buildNullValue(E->getType(), CGF.getLoc(E->getSourceRange())); + } + mlir::Value VisitGNUNullExpr(const GNUNullExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitOffsetOfExpr(OffsetOfExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *E); + mlir::Value VisitAddrLabelExpr(const AddrLabelExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitSizeOfPackExpr(SizeOfPackExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitPseudoObjectExpr(PseudoObjectExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitSYCLUniqueStableNameExpr(SYCLUniqueStableNameExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitOpaqueValueExpr(OpaqueValueExpr *E) { + llvm_unreachable("NYI"); + } + + /// Emits the address of the l-value, then loads and returns the result. + mlir::Value buildLoadOfLValue(const Expr *E) { + LValue LV = CGF.buildLValue(E); + // FIXME: add some akin to EmitLValueAlignmentAssumption(E, V); + return CGF.buildLoadOfLValue(LV, E->getExprLoc()).getScalarVal(); + } + + mlir::Value buildLoadOfLValue(LValue LV, SourceLocation Loc) { + return CGF.buildLoadOfLValue(LV, Loc).getScalarVal(); + } + + // l-values + mlir::Value VisitDeclRefExpr(DeclRefExpr *E) { + if (CIRGenFunction::ConstantEmission Constant = CGF.tryEmitAsConstant(E)) { + return CGF.buildScalarConstant(Constant, E); + } + return buildLoadOfLValue(E); + } + + mlir::Value VisitObjCSelectorExpr(ObjCSelectorExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitObjCProtocolExpr(ObjCProtocolExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitObjCIVarRefExpr(ObjCIvarRefExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitObjCMessageExpr(ObjCMessageExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitObjCIsaExpr(ObjCIsaExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitObjCAvailabilityCheckExpr(ObjCAvailabilityCheckExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitArraySubscriptExpr(ArraySubscriptExpr *E) { + // Do we need anything like TestAndClearIgnoreResultAssign()? + assert(!E->getBase()->getType()->isVectorType() && + "vector types not implemented"); + + // Emit subscript expressions in rvalue context's. For most cases, this + // just loads the lvalue formed by the subscript expr. However, we have to + // be careful, because the base of a vector subscript is occasionally an + // rvalue, so we can't get it as an lvalue. + return buildLoadOfLValue(E); + } + + mlir::Value VisitMatrixSubscriptExpr(MatrixSubscriptExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitShuffleVectorExpr(ShuffleVectorExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitConvertVectorExpr(ConvertVectorExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitMemberExpr(MemberExpr *E); + mlir::Value VisitExtVectorelementExpr(Expr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitCompoundLiteralEpxr(CompoundLiteralExpr *E) { + llvm_unreachable("NYI"); + } + + mlir::Value VisitInitListExpr(InitListExpr *E); + + mlir::Value VisitArrayInitIndexExpr(ArrayInitIndexExpr *E) { + llvm_unreachable("NYI"); + } + + mlir::Value VisitImplicitValueInitExpr(const ImplicitValueInitExpr *E) { + return buildNullValue(E->getType(), CGF.getLoc(E->getSourceRange())); + } + mlir::Value VisitExplicitCastExpr(ExplicitCastExpr *E) { + return VisitCastExpr(E); + } + mlir::Value VisitCastExpr(CastExpr *E); + mlir::Value VisitCallExpr(const CallExpr *E); + mlir::Value VisitStmtExpr(StmtExpr *E) { llvm_unreachable("NYI"); } + + // Unary Operators. + mlir::Value VisitUnaryPostDec(const UnaryOperator *E) { + LValue LV = buildLValue(E->getSubExpr()); + return buildScalarPrePostIncDec(E, LV, false, false); + } + mlir::Value VisitUnaryPostInc(const UnaryOperator *E) { + LValue LV = buildLValue(E->getSubExpr()); + return buildScalarPrePostIncDec(E, LV, true, false); + } + mlir::Value VisitUnaryPreDec(const UnaryOperator *E) { + LValue LV = buildLValue(E->getSubExpr()); + return buildScalarPrePostIncDec(E, LV, false, true); + } + mlir::Value VisitUnaryPreInc(const UnaryOperator *E) { + LValue LV = buildLValue(E->getSubExpr()); + return buildScalarPrePostIncDec(E, LV, true, true); + } + mlir::Value buildScalarPrePostIncDec(const UnaryOperator *E, LValue LV, + bool isInc, bool isPre) { + assert(!CGF.getLangOpts().OpenMP && "Not implemented"); + QualType type = E->getSubExpr()->getType(); + + int amount = (isInc ? 1 : -1); + bool atomicPHI = false; + mlir::Value value{}; + mlir::Value input{}; + + if (const AtomicType *atomicTy = type->getAs()) { + llvm_unreachable("no atomics inc/dec yet"); + } else { + value = buildLoadOfLValue(LV, E->getExprLoc()); + input = value; + } + + // NOTE: When possible, more frequent cases are handled first. + + // Special case of integer increment that we have to check first: bool++. + // Due to promotion rules, we get: + // bool++ -> bool = bool + 1 + // -> bool = (int)bool + 1 + // -> bool = ((int)bool + 1 != 0) + // An interesting aspect of this is that increment is always true. + // Decrement does not have this property. + if (isInc && type->isBooleanType()) { + llvm_unreachable("inc simplification for booleans not implemented yet"); + + // NOTE: We likely want the code below, but loading/store booleans need to + // work first. See CIRGenFunction::buildFromMemory(). + value = Builder.create( + CGF.getLoc(E->getExprLoc()), CGF.getCIRType(type), + Builder.getCIRBoolAttr(true)); + } else if (type->isIntegerType()) { + QualType promotedType; + bool canPerformLossyDemotionCheck = false; + if (CGF.getContext().isPromotableIntegerType(type)) { + promotedType = CGF.getContext().getPromotedIntegerType(type); + assert(promotedType != type && "Shouldn't promote to the same type."); + canPerformLossyDemotionCheck = true; + canPerformLossyDemotionCheck &= + CGF.getContext().getCanonicalType(type) != + CGF.getContext().getCanonicalType(promotedType); + canPerformLossyDemotionCheck &= + PromotionIsPotentiallyEligibleForImplicitIntegerConversionCheck( + type, promotedType); + + // TODO(cir): Currently, we store bitwidths in CIR types only for + // integers. This might also be required for other types. + auto srcCirTy = ConvertType(type).dyn_cast(); + auto promotedCirTy = ConvertType(type).dyn_cast(); + assert(srcCirTy && promotedCirTy && "Expected integer type"); + + assert( + (!canPerformLossyDemotionCheck || + type->isSignedIntegerOrEnumerationType() || + promotedType->isSignedIntegerOrEnumerationType() || + srcCirTy.getWidth() == promotedCirTy.getWidth()) && + "The following check expects that if we do promotion to different " + "underlying canonical type, at least one of the types (either " + "base or promoted) will be signed, or the bitwidths will match."); + } + + if (CGF.SanOpts.hasOneOf( + SanitizerKind::ImplicitIntegerArithmeticValueChange) && + canPerformLossyDemotionCheck) { + llvm_unreachable( + "perform lossy demotion case for inc/dec not implemented yet"); + } else if (E->canOverflow() && type->isSignedIntegerOrEnumerationType()) { + value = buildIncDecConsiderOverflowBehavior(E, value, isInc); + } else if (E->canOverflow() && type->isUnsignedIntegerType() && + CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow)) { + llvm_unreachable( + "unsigned integer overflow sanitized inc/dec not implemented"); + } else { + auto Kind = E->isIncrementOp() ? mlir::cir::UnaryOpKind::Inc + : mlir::cir::UnaryOpKind::Dec; + // NOTE(CIR): clang calls CreateAdd but folds this to a unary op + value = buildUnaryOp(E, Kind, input); + } + // Next most common: pointer increment. + } else if (const PointerType *ptr = type->getAs()) { + QualType type = ptr->getPointeeType(); + if (const VariableArrayType *vla = + CGF.getContext().getAsVariableArrayType(type)) { + // VLA types don't have constant size. + llvm_unreachable("NYI"); + } else if (type->isFunctionType()) { + // Arithmetic on function pointers (!) is just +-1. + llvm_unreachable("NYI"); + } else { + // For everything else, we can just do a simple increment. + auto loc = CGF.getLoc(E->getSourceRange()); + auto &builder = CGF.getBuilder(); + auto amt = builder.getSInt32(amount, loc); + if (CGF.getLangOpts().isSignedOverflowDefined()) { + llvm_unreachable("NYI"); + } else { + value = builder.create(loc, value.getType(), + value, amt); + assert(!UnimplementedFeature::emitCheckedInBoundsGEP()); + } + } + } else if (type->isVectorType()) { + llvm_unreachable("no vector inc/dec yet"); + } else if (type->isRealFloatingType()) { + auto isFloatOrDouble = type->isSpecificBuiltinType(BuiltinType::Float) || + type->isSpecificBuiltinType(BuiltinType::Double); + assert(isFloatOrDouble && "Non-float/double NYI"); + + // Create the inc/dec operation. + auto kind = + (isInc ? mlir::cir::UnaryOpKind::Inc : mlir::cir::UnaryOpKind::Dec); + value = buildUnaryOp(E, kind, input); + + } else if (type->isFixedPointType()) { + llvm_unreachable("no fixed point inc/dec yet"); + } else { + assert(type->castAs()); + llvm_unreachable("no objc pointer type inc/dec yet"); + } + + if (atomicPHI) { + llvm_unreachable("NYI"); + } + + CIRGenFunction::SourceLocRAIIObject sourceloc{ + CGF, CGF.getLoc(E->getSourceRange())}; + + // Store the updated result through the lvalue + if (LV.isBitField()) + llvm_unreachable("no bitfield inc/dec yet"); + else + CGF.buildStoreThroughLValue(RValue::get(value), LV); + + // If this is a postinc, return the value read from memory, otherwise use + // the updated value. + return isPre ? value : input; + } + + mlir::Value buildIncDecConsiderOverflowBehavior(const UnaryOperator *E, + mlir::Value InVal, + bool IsInc) { + // NOTE(CIR): The SignedOverflowBehavior is attached to the global ModuleOp + // and the nsw behavior is handled during lowering. + auto Kind = E->isIncrementOp() ? mlir::cir::UnaryOpKind::Inc + : mlir::cir::UnaryOpKind::Dec; + switch (CGF.getLangOpts().getSignedOverflowBehavior()) { + case LangOptions::SOB_Defined: + return buildUnaryOp(E, Kind, InVal); + case LangOptions::SOB_Undefined: + if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) + return buildUnaryOp(E, Kind, InVal); + llvm_unreachable( + "inc/dec overflow behavior SOB_Undefined not implemented yet"); + break; + case LangOptions::SOB_Trapping: + if (!E->canOverflow()) + return buildUnaryOp(E, Kind, InVal); + llvm_unreachable( + "inc/dec overflow behavior SOB_Trapping not implemented yet"); + break; + } + } + + mlir::Value VisitUnaryAddrOf(const UnaryOperator *E) { + assert(!llvm::isa(E->getType()) && "not implemented"); + return CGF.buildLValue(E->getSubExpr()).getPointer(); + } + + mlir::Value VisitUnaryDeref(const UnaryOperator *E) { + if (E->getType()->isVoidType()) + return Visit(E->getSubExpr()); // the actual value should be unused + return buildLoadOfLValue(E); + } + mlir::Value VisitUnaryPlus(const UnaryOperator *E) { + // NOTE(cir): QualType function parameter still not used, so don´t replicate + // it here yet. + QualType promotionTy = getPromotionType(E->getSubExpr()->getType()); + auto result = VisitPlus(E, promotionTy); + if (result && !promotionTy.isNull()) + assert(0 && "not implemented yet"); + return buildUnaryOp(E, mlir::cir::UnaryOpKind::Plus, result); + } + + mlir::Value VisitPlus(const UnaryOperator *E, QualType PromotionType) { + // This differs from gcc, though, most likely due to a bug in gcc. + TestAndClearIgnoreResultAssign(); + if (!PromotionType.isNull()) + assert(0 && "scalar promotion not implemented yet"); + return Visit(E->getSubExpr()); + } + + mlir::Value VisitUnaryMinus(const UnaryOperator *E) { + // NOTE(cir): QualType function parameter still not used, so don´t replicate + // it here yet. + QualType promotionTy = getPromotionType(E->getSubExpr()->getType()); + auto result = VisitMinus(E, promotionTy); + if (result && !promotionTy.isNull()) + assert(0 && "not implemented yet"); + return buildUnaryOp(E, mlir::cir::UnaryOpKind::Minus, result); + } + + mlir::Value VisitMinus(const UnaryOperator *E, QualType PromotionType) { + TestAndClearIgnoreResultAssign(); + if (!PromotionType.isNull()) + assert(0 && "scalar promotion not implemented yet"); + + // NOTE: LLVM codegen will lower this directly to either a FNeg + // or a Sub instruction. In CIR this will be handled later in LowerToLLVM. + + return Visit(E->getSubExpr()); + } + + mlir::Value VisitUnaryNot(const UnaryOperator *E) { + TestAndClearIgnoreResultAssign(); + mlir::Value op = Visit(E->getSubExpr()); + return buildUnaryOp(E, mlir::cir::UnaryOpKind::Not, op); + } + + mlir::Value VisitUnaryLNot(const UnaryOperator *E); + mlir::Value VisitUnaryReal(const UnaryOperator *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitUnaryImag(const UnaryOperator *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitUnaryExtension(const UnaryOperator *E) { + llvm_unreachable("NYI"); + } + + mlir::Value buildUnaryOp(const UnaryOperator *E, mlir::cir::UnaryOpKind kind, + mlir::Value input) { + return Builder.create( + CGF.getLoc(E->getSourceRange().getBegin()), + CGF.getCIRType(E->getType()), kind, input); + } + + // C++ + mlir::Value VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitSourceLocExpr(SourceLocExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitCXXDefaultArgExpr(CXXDefaultArgExpr *DAE) { + CIRGenFunction::CXXDefaultArgExprScope Scope(CGF, DAE); + return Visit(DAE->getExpr()); + } + mlir::Value VisitCXXDefaultInitExpr(CXXDefaultInitExpr *DIE) { + CIRGenFunction::CXXDefaultInitExprScope Scope(CGF, DIE); + return Visit(DIE->getExpr()); + } + + mlir::Value VisitCXXThisExpr(CXXThisExpr *TE) { return CGF.LoadCXXThis(); } + + mlir::Value VisitExprWithCleanups(ExprWithCleanups *E); + mlir::Value VisitCXXNewExpr(const CXXNewExpr *E) { + return CGF.buildCXXNewExpr(E); + } + mlir::Value VisitCXXDeleteExpr(const CXXDeleteExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitTypeTraitExpr(const TypeTraitExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value + VisitConceptSpecializationExpr(const ConceptSpecializationExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitRequiresExpr(const RequiresExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitArrayTypeTraitExpr(const ArrayTypeTraitExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitExpressionTraitExpr(const ExpressionTraitExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitCXXPseudoDestructorExpr(const CXXPseudoDestructorExpr *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitCXXNullPtrLiteralExpr(CXXNullPtrLiteralExpr *E) { + return buildNullValue(E->getType(), CGF.getLoc(E->getSourceRange())); + } + mlir::Value VisitCXXThrowExpr(CXXThrowExpr *E) { + CGF.buildCXXThrowExpr(E); + return nullptr; + } + mlir::Value VisitCXXNoexceptExpr(CXXNoexceptExpr *E) { + llvm_unreachable("NYI"); + } + + /// Perform a pointer to boolean conversion. + mlir::Value buildPointerToBoolConversion(mlir::Value V, QualType QT) { + // TODO(cir): comparing the ptr to null is done when lowering CIR to LLVM. + // We might want to have a separate pass for these types of conversions. + return CGF.getBuilder().createPtrToBoolCast(V); + } + + // Comparisons. +#define VISITCOMP(CODE) \ + mlir::Value VisitBin##CODE(const BinaryOperator *E) { return buildCmp(E); } + VISITCOMP(LT) + VISITCOMP(GT) + VISITCOMP(LE) + VISITCOMP(GE) + VISITCOMP(EQ) + VISITCOMP(NE) +#undef VISITCOMP + + mlir::Value VisitBinAssign(const BinaryOperator *E); + mlir::Value VisitBinLAnd(const BinaryOperator *B); + mlir::Value VisitBinLOr(const BinaryOperator *B); + mlir::Value VisitBinComma(const BinaryOperator *E) { + CGF.buildIgnoredExpr(E->getLHS()); + // NOTE: We don't need to EnsureInsertPoint() like LLVM codegen. + return Visit(E->getRHS()); + } + + mlir::Value VisitBinPtrMemD(const Expr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitBinPtrMemI(const Expr *E) { llvm_unreachable("NYI"); } + + mlir::Value VisitCXXRewrittenBinaryOperator(CXXRewrittenBinaryOperator *E) { + llvm_unreachable("NYI"); + } + + // Other Operators. + mlir::Value VisitBlockExpr(const BlockExpr *E) { llvm_unreachable("NYI"); } + mlir::Value + VisitAbstractConditionalOperator(const AbstractConditionalOperator *E); + mlir::Value VisitChooseExpr(ChooseExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitVAArgExpr(VAArgExpr *VE); + mlir::Value VisitObjCStringLiteral(const ObjCStringLiteral *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitObjCBoxedExpr(ObjCBoxedExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitObjCArrayLiteral(ObjCArrayLiteral *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitObjCDictionaryLiteral(ObjCDictionaryLiteral *E) { + llvm_unreachable("NYI"); + } + mlir::Value VisitAsTypeExpr(AsTypeExpr *E) { llvm_unreachable("NYI"); } + mlir::Value VisitAtomicExpr(AtomicExpr *E) { llvm_unreachable("NYI"); } + + // Emit a conversion from the specified type to the specified destination + // type, both of which are CIR scalar types. + struct ScalarConversionOpts { + bool TreatBooleanAsSigned; + bool EmitImplicitIntegerTruncationChecks; + bool EmitImplicitIntegerSignChangeChecks; + + ScalarConversionOpts() + : TreatBooleanAsSigned(false), + EmitImplicitIntegerTruncationChecks(false), + EmitImplicitIntegerSignChangeChecks(false) {} + + ScalarConversionOpts(clang::SanitizerSet SanOpts) + : TreatBooleanAsSigned(false), + EmitImplicitIntegerTruncationChecks( + SanOpts.hasOneOf(SanitizerKind::ImplicitIntegerTruncation)), + EmitImplicitIntegerSignChangeChecks( + SanOpts.has(SanitizerKind::ImplicitIntegerSignChange)) {} + }; + mlir::Value buildScalarCast(mlir::Value Src, QualType SrcType, + QualType DstType, mlir::Type SrcTy, + mlir::Type DstTy, ScalarConversionOpts Opts); + + BinOpInfo buildBinOps(const BinaryOperator *E) { + BinOpInfo Result; + Result.LHS = Visit(E->getLHS()); + Result.RHS = Visit(E->getRHS()); + Result.Ty = E->getType(); + Result.Opcode = E->getOpcode(); + Result.Loc = E->getSourceRange(); + // TODO: Result.FPFeatures + Result.E = E; + return Result; + } + + mlir::Value buildMul(const BinOpInfo &Ops); + mlir::Value buildDiv(const BinOpInfo &Ops); + mlir::Value buildRem(const BinOpInfo &Ops); + mlir::Value buildAdd(const BinOpInfo &Ops); + mlir::Value buildSub(const BinOpInfo &Ops); + mlir::Value buildShl(const BinOpInfo &Ops); + mlir::Value buildShr(const BinOpInfo &Ops); + mlir::Value buildAnd(const BinOpInfo &Ops); + mlir::Value buildXor(const BinOpInfo &Ops); + mlir::Value buildOr(const BinOpInfo &Ops); + + LValue buildCompoundAssignLValue( + const CompoundAssignOperator *E, + mlir::Value (ScalarExprEmitter::*F)(const BinOpInfo &), + mlir::Value &Result); + mlir::Value + buildCompoundAssign(const CompoundAssignOperator *E, + mlir::Value (ScalarExprEmitter::*F)(const BinOpInfo &)); + + // TODO(cir): Candidate to be in a common AST helper between CIR and LLVM + // codegen. + QualType getPromotionType(QualType Ty) { + if (auto *CT = Ty->getAs()) { + llvm_unreachable("NYI"); + } + if (Ty.UseExcessPrecision(CGF.getContext())) + llvm_unreachable("NYI"); + return QualType(); + } + + // Binary operators and binary compound assignment operators. +#define HANDLEBINOP(OP) \ + mlir::Value VisitBin##OP(const BinaryOperator *E) { \ + return build##OP(buildBinOps(E)); \ + } \ + mlir::Value VisitBin##OP##Assign(const CompoundAssignOperator *E) { \ + return buildCompoundAssign(E, &ScalarExprEmitter::build##OP); \ + } + + HANDLEBINOP(Mul) + HANDLEBINOP(Div) + HANDLEBINOP(Rem) + HANDLEBINOP(Add) + HANDLEBINOP(Sub) + HANDLEBINOP(Shl) + HANDLEBINOP(Shr) + HANDLEBINOP(And) + HANDLEBINOP(Xor) + HANDLEBINOP(Or) +#undef HANDLEBINOP + + mlir::Value buildCmp(const BinaryOperator *E) { + mlir::Value Result; + QualType LHSTy = E->getLHS()->getType(); + QualType RHSTy = E->getRHS()->getType(); + + if (const MemberPointerType *MPT = LHSTy->getAs()) { + assert(0 && "not implemented"); + } else if (!LHSTy->isAnyComplexType() && !RHSTy->isAnyComplexType()) { + BinOpInfo BOInfo = buildBinOps(E); + mlir::Value LHS = BOInfo.LHS; + mlir::Value RHS = BOInfo.RHS; + + if (LHSTy->isVectorType()) { + // Cannot handle any vector just yet. + assert(0 && "not implemented"); + // If AltiVec, the comparison results in a numeric type, so we use + // intrinsics comparing vectors and giving 0 or 1 as a result + if (!E->getType()->isVectorType()) + assert(0 && "not implemented"); + } + if (BOInfo.isFixedPointOp()) { + assert(0 && "not implemented"); + } else { + // FIXME(cir): handle another if above for CIR equivalent on + // LHSTy->hasSignedIntegerRepresentation() + + // Unsigned integers and pointers. + if (CGF.CGM.getCodeGenOpts().StrictVTablePointers && + LHS.getType().isa() && + RHS.getType().isa()) { + llvm_unreachable("NYI"); + } + + mlir::cir::CmpOpKind Kind; + switch (E->getOpcode()) { + case BO_LT: + Kind = mlir::cir::CmpOpKind::lt; + break; + case BO_GT: + Kind = mlir::cir::CmpOpKind::gt; + break; + case BO_LE: + Kind = mlir::cir::CmpOpKind::le; + break; + case BO_GE: + Kind = mlir::cir::CmpOpKind::ge; + break; + case BO_EQ: + Kind = mlir::cir::CmpOpKind::eq; + break; + case BO_NE: + Kind = mlir::cir::CmpOpKind::ne; + break; + default: + llvm_unreachable("unsupported"); + } + + return Builder.create(CGF.getLoc(BOInfo.Loc), + CGF.getCIRType(BOInfo.Ty), Kind, + BOInfo.LHS, BOInfo.RHS); + } + + // If this is a vector comparison, sign extend the result to the + // appropriate vector integer type and return it (don't convert to + // bool). + if (LHSTy->isVectorType()) + assert(0 && "not implemented"); + } else { // Complex Comparison: can only be an equality comparison. + assert(0 && "not implemented"); + } + + return buildScalarConversion(Result, CGF.getContext().BoolTy, E->getType(), + E->getExprLoc()); + } + + mlir::Value buildFloatToBoolConversion(mlir::Value src, mlir::Location loc) { + auto boolTy = Builder.getBoolTy(); + return Builder.create( + loc, boolTy, mlir::cir::CastKind::float_to_bool, src); + } + + mlir::Value buildIntToBoolConversion(mlir::Value srcVal, mlir::Location loc) { + // Because of the type rules of C, we often end up computing a + // logical value, then zero extending it to int, then wanting it + // as a logical value again. + // TODO: optimize this common case here or leave it for later + // CIR passes? + mlir::Type boolTy = CGF.getCIRType(CGF.getContext().BoolTy); + return Builder.create( + loc, boolTy, mlir::cir::CastKind::int_to_bool, srcVal); + } + + /// Convert the specified expression value to a boolean (!cir.bool) truth + /// value. This is equivalent to "Val != 0". + mlir::Value buildConversionToBool(mlir::Value Src, QualType SrcType, + mlir::Location loc) { + assert(SrcType.isCanonical() && "EmitScalarConversion strips typedefs"); + + if (SrcType->isRealFloatingType()) + return buildFloatToBoolConversion(Src, loc); + + if (auto *MPT = llvm::dyn_cast(SrcType)) + assert(0 && "not implemented"); + + if (SrcType->isIntegerType()) + return buildIntToBoolConversion(Src, loc); + + assert(Src.getType().isa<::mlir::cir::PointerType>()); + return buildPointerToBoolConversion(Src, SrcType); + } + + /// 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 + buildScalarConversion(mlir::Value Src, QualType SrcType, QualType DstType, + SourceLocation Loc, + ScalarConversionOpts Opts = ScalarConversionOpts()) { + // All conversions involving fixed point types should be handled by the + // buildFixedPoint family functions. This is done to prevent bloating up + // this function more, and although fixed point numbers are represented by + // integers, we do not want to follow any logic that assumes they should be + // treated as integers. + // TODO(leonardchan): When necessary, add another if statement checking for + // conversions to fixed point types from other types. + if (SrcType->isFixedPointType()) { + llvm_unreachable("not implemented"); + } else if (DstType->isFixedPointType()) { + llvm_unreachable("not implemented"); + } + + SrcType = CGF.getContext().getCanonicalType(SrcType); + DstType = CGF.getContext().getCanonicalType(DstType); + if (SrcType == DstType) + return Src; + + if (DstType->isVoidType()) + return nullptr; + + mlir::Type SrcTy = Src.getType(); + + // Handle conversions to bool first, they are special: comparisons against + // 0. + if (DstType->isBooleanType()) + return buildConversionToBool(Src, SrcType, CGF.getLoc(Loc)); + + mlir::Type DstTy = ConvertType(DstType); + + // Cast from half through float if half isn't a native type. + if (SrcType->isHalfType() && + !CGF.getContext().getLangOpts().NativeHalfType) { + llvm_unreachable("not implemented"); + } + + // TODO(cir): LLVM codegen ignore conversions like int -> uint, + // is there anything to be done for CIR here? + if (SrcTy == DstTy) { + if (Opts.EmitImplicitIntegerSignChangeChecks) + llvm_unreachable("not implemented"); + return Src; + } + + // Handle pointer conversions next: pointers can only be converted to/from + // other pointers and integers. + if (DstTy.isa<::mlir::cir::PointerType>()) { + llvm_unreachable("not implemented"); + } + + if (SrcTy.isa<::mlir::cir::PointerType>()) { + // Must be a ptr to int cast. + assert(CGF.getBuilder().isInt(DstTy) && "not ptr->int?"); + llvm_unreachable("not implemented"); + } + + // A scalar can be splatted to an extended vector of the same element type + if (DstType->isExtVectorType() && !SrcType->isVectorType()) { + // Sema should add casts to make sure that the source expression's type + // is the same as the vector's element type (sans qualifiers) + assert(DstType->castAs()->getElementType().getTypePtr() == + SrcType.getTypePtr() && + "Splatted expr doesn't match with vector element type?"); + + llvm_unreachable("not implemented"); + } + + if (SrcType->isMatrixType() && DstType->isMatrixType()) + llvm_unreachable("not implemented"); + + // TODO(CIR): Support VectorTypes + + // Finally, we have the arithmetic types: real int/float. + mlir::Value Res = nullptr; + mlir::Type ResTy = DstTy; + + // An overflowing conversion has undefined behavior if eitehr the source + // type or the destination type is a floating-point type. However, we + // consider the range of representable values for all floating-point types + // to be [-inf,+inf], so no overflow can ever happen when the destination + // type is a floating-point type. + if (CGF.SanOpts.has(SanitizerKind::FloatCastOverflow)) + llvm_unreachable("NYI"); + + // Cast to half through float if half isn't a native type. + if (DstType->isHalfType() && + !CGF.getContext().getLangOpts().NativeHalfType) { + llvm_unreachable("NYI"); + } + + Res = buildScalarCast(Src, SrcType, DstType, SrcTy, DstTy, Opts); + + if (DstTy != ResTy) { + llvm_unreachable("NYI"); + } + + if (Opts.EmitImplicitIntegerTruncationChecks) + llvm_unreachable("NYI"); + + if (Opts.EmitImplicitIntegerSignChangeChecks) + llvm_unreachable("NYI"); + + return Res; + } +}; + +} // namespace + +/// Emit the computation of the specified expression of scalar type, +/// ignoring the result. +mlir::Value CIRGenFunction::buildScalarExpr(const Expr *E) { + assert(E && hasScalarEvaluationKind(E->getType()) && + "Invalid scalar expression to emit"); + + return ScalarExprEmitter(*this, builder).Visit(const_cast(E)); +} + +[[maybe_unused]] static bool MustVisitNullValue(const Expr *E) { + // If a null pointer expression's type is the C++0x nullptr_t, then + // it's not necessarily a simple constant and it must be evaluated + // for its potential side effects. + return E->getType()->isNullPtrType(); +} + +/// If \p E is a widened promoted integer, get its base (unpromoted) type. +static std::optional getUnwidenedIntegerType(const ASTContext &Ctx, + const Expr *E) { + const Expr *Base = E->IgnoreImpCasts(); + if (E == Base) + return std::nullopt; + + QualType BaseTy = Base->getType(); + if (!Ctx.isPromotableIntegerType(BaseTy) || + Ctx.getTypeSize(BaseTy) >= Ctx.getTypeSize(E->getType())) + return std::nullopt; + + return BaseTy; +} + +/// Check if \p E is a widened promoted integer. +[[maybe_unused]] static bool IsWidenedIntegerOp(const ASTContext &Ctx, + const Expr *E) { + return getUnwidenedIntegerType(Ctx, E).has_value(); +} + +/// Check if we can skip the overflow check for \p Op. +[[maybe_unused]] static bool CanElideOverflowCheck(const ASTContext &Ctx, + const BinOpInfo &Op) { + assert((isa(Op.E) || isa(Op.E)) && + "Expected a unary or binary operator"); + + // If the binop has constant inputs and we can prove there is no overflow, + // we can elide the overflow check. + if (!Op.mayHaveIntegerOverflow()) + return true; + + // If a unary op has a widened operand, the op cannot overflow. + if (const auto *UO = dyn_cast(Op.E)) + return !UO->canOverflow(); + + // We usually don't need overflow checks for binops with widened operands. + // Multiplication with promoted unsigned operands is a special case. + const auto *BO = cast(Op.E); + auto OptionalLHSTy = getUnwidenedIntegerType(Ctx, BO->getLHS()); + if (!OptionalLHSTy) + return false; + + auto OptionalRHSTy = getUnwidenedIntegerType(Ctx, BO->getRHS()); + if (!OptionalRHSTy) + return false; + + QualType LHSTy = *OptionalLHSTy; + QualType RHSTy = *OptionalRHSTy; + + // This is the simple case: binops without unsigned multiplication, and with + // widened operands. No overflow check is needed here. + if ((Op.Opcode != BO_Mul && Op.Opcode != BO_MulAssign) || + !LHSTy->isUnsignedIntegerType() || !RHSTy->isUnsignedIntegerType()) + return true; + + // For unsigned multiplication the overflow check can be elided if either one + // of the unpromoted types are less than half the size of the promoted type. + unsigned PromotedSize = Ctx.getTypeSize(Op.E->getType()); + return (2 * Ctx.getTypeSize(LHSTy)) < PromotedSize || + (2 * Ctx.getTypeSize(RHSTy)) < PromotedSize; +} + +/// Emit pointer + index arithmetic. +static mlir::Value buildPointerArithmetic(CIRGenFunction &CGF, + const BinOpInfo &op, + bool isSubtraction) { + // Must have binary (not unary) expr here. Unary pointer + // increment/decrement doesn't use this path. + const BinaryOperator *expr = cast(op.E); + + mlir::Value pointer = op.LHS; + Expr *pointerOperand = expr->getLHS(); + mlir::Value index = op.RHS; + Expr *indexOperand = expr->getRHS(); + + // In a subtraction, the LHS is always the pointer. + if (!isSubtraction && !pointer.getType().isa()) { + std::swap(pointer, index); + std::swap(pointerOperand, indexOperand); + } + + bool isSigned = indexOperand->getType()->isSignedIntegerOrEnumerationType(); + + // Some versions of glibc and gcc use idioms (particularly in their malloc + // routines) that add a pointer-sized integer (known to be a pointer value) + // to a null pointer in order to cast the value back to an integer or as + // part of a pointer alignment algorithm. This is undefined behavior, but + // we'd like to be able to compile programs that use it. + // + // Normally, we'd generate a GEP with a null-pointer base here in response + // to that code, but it's also UB to dereference a pointer created that + // way. Instead (as an acknowledged hack to tolerate the idiom) we will + // generate a direct cast of the integer value to a pointer. + // + // The idiom (p = nullptr + N) is not met if any of the following are true: + // + // The operation is subtraction. + // The index is not pointer-sized. + // The pointer type is not byte-sized. + // + if (BinaryOperator::isNullPointerArithmeticExtension( + CGF.getContext(), op.Opcode, expr->getLHS(), expr->getRHS())) + llvm_unreachable("null pointer arithmetic extension is NYI"); + + if (UnimplementedFeature::dataLayoutGetIndexTypeSizeInBits()) { + // TODO(cir): original codegen zero/sign-extends the index to the same width + // as the pointer. Since CIR's pointer stride doesn't care about that, it's + // skiped here. + llvm_unreachable("target-specific pointer width is NYI"); + } + + // If this is subtraction, negate the index. + if (isSubtraction) + index = CGF.getBuilder().createNeg(index); + + if (CGF.SanOpts.has(SanitizerKind::ArrayBounds)) + llvm_unreachable("array bounds sanitizer is NYI"); + + const PointerType *pointerType = + pointerOperand->getType()->getAs(); + if (!pointerType) + llvm_unreachable("ObjC is NYI"); + + QualType elementType = pointerType->getPointeeType(); + if (const VariableArrayType *vla = + CGF.getContext().getAsVariableArrayType(elementType)) + llvm_unreachable("VLA pointer arithmetic is NYI"); + + // Explicitly handle GNU void* and function pointer arithmetic extensions. The + // GNU void* casts amount to no-ops since our void* type is i8*, but this is + // future proof. + if (elementType->isVoidType() || elementType->isFunctionType()) + llvm_unreachable("GNU void* and func ptr arithmetic extensions are NYI"); + + mlir::Type elemTy = CGF.convertTypeForMem(elementType); + if (CGF.getLangOpts().isSignedOverflowDefined()) + llvm_unreachable("ptr arithmetic with signed overflow is NYI"); + + return CGF.buildCheckedInBoundsGEP(elemTy, pointer, index, isSigned, + isSubtraction, op.E->getExprLoc()); +} + +mlir::Value ScalarExprEmitter::buildMul(const BinOpInfo &Ops) { + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Mul, + Ops.LHS, Ops.RHS); +} +mlir::Value ScalarExprEmitter::buildDiv(const BinOpInfo &Ops) { + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Div, + Ops.LHS, Ops.RHS); +} +mlir::Value ScalarExprEmitter::buildRem(const BinOpInfo &Ops) { + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Rem, + Ops.LHS, Ops.RHS); +} + +mlir::Value ScalarExprEmitter::buildAdd(const BinOpInfo &Ops) { + if (Ops.LHS.getType().isa() || + Ops.RHS.getType().isa()) + return buildPointerArithmetic(CGF, Ops, /*isSubtraction=*/false); + + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Add, + Ops.LHS, Ops.RHS); +} + +mlir::Value ScalarExprEmitter::buildSub(const BinOpInfo &Ops) { + // The LHS is always a pointer if either side is. + if (!Ops.LHS.getType().isa()) { + if (Ops.Ty->isSignedIntegerOrEnumerationType()) { + switch (CGF.getLangOpts().getSignedOverflowBehavior()) { + case LangOptions::SOB_Defined: { + llvm_unreachable("NYI"); + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), + mlir::cir::BinOpKind::Sub, Ops.LHS, Ops.RHS); + } + case LangOptions::SOB_Undefined: + if (!CGF.SanOpts.has(SanitizerKind::SignedIntegerOverflow)) + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), + mlir::cir::BinOpKind::Sub, Ops.LHS, Ops.RHS); + [[fallthrough]]; + case LangOptions::SOB_Trapping: + if (CanElideOverflowCheck(CGF.getContext(), Ops)) + llvm_unreachable("NYI"); + llvm_unreachable("NYI"); + } + } + + if (Ops.Ty->isConstantMatrixType()) { + llvm_unreachable("NYI"); + } + + if (Ops.Ty->isUnsignedIntegerType() && + CGF.SanOpts.has(SanitizerKind::UnsignedIntegerOverflow) && + !CanElideOverflowCheck(CGF.getContext(), Ops)) + llvm_unreachable("NYI"); + + assert(!UnimplementedFeature::cirVectorType()); + if (Ops.LHS.getType().isa()) { + CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(CGF, Ops.FPFeatures); + return Builder.createFSub(Ops.LHS, Ops.RHS); + } + + if (Ops.isFixedPointOp()) + llvm_unreachable("NYI"); + + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Sub, + Ops.LHS, Ops.RHS); + } + + // If the RHS is not a pointer, then we have normal pointer + // arithmetic. + if (!Ops.RHS.getType().isa()) + return buildPointerArithmetic(CGF, Ops, /*isSubtraction=*/true); + + // Otherwise, this is a pointer subtraction + + // Do the raw subtraction part. + // + // TODO(cir): note for LLVM lowering out of this; when expanding this into + // LLVM we shall take VLA's, division by element size, etc. + // + // See more in `EmitSub` in CGExprScalar.cpp. + assert(!UnimplementedFeature::llvmLoweringPtrDiffConsidersPointee()); + return Builder.create(CGF.getLoc(Ops.Loc), + CGF.PtrDiffTy, Ops.LHS, Ops.RHS); +} + +mlir::Value ScalarExprEmitter::buildShl(const BinOpInfo &Ops) { + // TODO: This misses out on the sanitizer check below. + if (Ops.isFixedPointOp()) + llvm_unreachable("NYI"); + + // CIR accepts shift between different types, meaning nothing special + // to be done here. OTOH, LLVM requires the LHS and RHS to be the same type: + // promote or truncate the RHS to the same size as the LHS. + + bool SanitizeSignedBase = CGF.SanOpts.has(SanitizerKind::ShiftBase) && + Ops.Ty->hasSignedIntegerRepresentation() && + !CGF.getLangOpts().isSignedOverflowDefined() && + !CGF.getLangOpts().CPlusPlus20; + bool SanitizeUnsignedBase = + CGF.SanOpts.has(SanitizerKind::UnsignedShiftBase) && + Ops.Ty->hasUnsignedIntegerRepresentation(); + bool SanitizeBase = SanitizeSignedBase || SanitizeUnsignedBase; + bool SanitizeExponent = CGF.SanOpts.has(SanitizerKind::ShiftExponent); + + // OpenCL 6.3j: shift values are effectively % word size of LHS. + if (CGF.getLangOpts().OpenCL) + llvm_unreachable("NYI"); + else if ((SanitizeBase || SanitizeExponent) && + Ops.LHS.getType().isa()) { + llvm_unreachable("NYI"); + } + + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), Ops.LHS, Ops.RHS, + CGF.getBuilder().getUnitAttr()); +} + +mlir::Value ScalarExprEmitter::buildShr(const BinOpInfo &Ops) { + // TODO: This misses out on the sanitizer check below. + if (Ops.isFixedPointOp()) + llvm_unreachable("NYI"); + + // CIR accepts shift between different types, meaning nothing special + // to be done here. OTOH, LLVM requires the LHS and RHS to be the same type: + // promote or truncate the RHS to the same size as the LHS. + + // OpenCL 6.3j: shift values are effectively % word size of LHS. + if (CGF.getLangOpts().OpenCL) + llvm_unreachable("NYI"); + else if (CGF.SanOpts.has(SanitizerKind::ShiftExponent) && + Ops.LHS.getType().isa()) { + llvm_unreachable("NYI"); + } + + // Note that we don't need to distinguish unsigned treatment at this + // point since it will be handled later by LLVM lowering. + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), Ops.LHS, Ops.RHS); +} + +mlir::Value ScalarExprEmitter::buildAnd(const BinOpInfo &Ops) { + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::And, + Ops.LHS, Ops.RHS); +} +mlir::Value ScalarExprEmitter::buildXor(const BinOpInfo &Ops) { + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Xor, + Ops.LHS, Ops.RHS); +} +mlir::Value ScalarExprEmitter::buildOr(const BinOpInfo &Ops) { + return Builder.create( + CGF.getLoc(Ops.Loc), CGF.getCIRType(Ops.Ty), mlir::cir::BinOpKind::Or, + Ops.LHS, Ops.RHS); +} + +// 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(); + + // These cases are generally not written to ignore the result of evaluating + // their sub-expressions, so we clear this now. + bool Ignored = TestAndClearIgnoreResultAssign(); + (void)Ignored; + + // Since almost all cast kinds apply to scalars, this switch doesn't have a + // default case, so the compiler will warn on a missing case. The cases are + // in the same order as in the CastKind enum. + switch (Kind) { + case clang::CK_Dependent: + llvm_unreachable("dependent cast kind in CIR gen!"); + case clang::CK_BuiltinFnToFnPtr: + llvm_unreachable("builtin functions are handled elsewhere"); + + case CK_LValueBitCast: + llvm_unreachable("NYI"); + case CK_ObjCObjectLValueCast: + llvm_unreachable("NYI"); + case CK_LValueToRValueBitCast: + llvm_unreachable("NYI"); + case CK_CPointerToObjCPointerCast: + case CK_BlockPointerToObjCPointerCast: + case CK_AnyPointerToBlockPointerCast: + case CK_BitCast: { + auto Src = Visit(const_cast(E)); + mlir::Type DstTy = CGF.convertType(DestTy); + + assert(!UnimplementedFeature::cirVectorType()); + assert(!UnimplementedFeature::addressSpace()); + if (CGF.SanOpts.has(SanitizerKind::CFIUnrelatedCast)) { + llvm_unreachable("NYI"); + } + + if (CGF.CGM.getCodeGenOpts().StrictVTablePointers) { + llvm_unreachable("NYI"); + } + + // Update heapallocsite metadata when there is an explicit pointer cast. + assert(!UnimplementedFeature::addHeapAllocSiteMetadata()); + + // If Src is a fixed vector and Dst is a scalable vector, and both have the + // same element type, use the llvm.vector.insert intrinsic to perform the + // bitcast. + assert(!UnimplementedFeature::cirVectorType()); + + // If Src is a scalable vector and Dst is a fixed vector, and both have the + // same element type, use the llvm.vector.extract intrinsic to perform the + // bitcast. + assert(!UnimplementedFeature::cirVectorType()); + + // Perform VLAT <-> VLST bitcast through memory. + // TODO: since the llvm.experimental.vector.{insert,extract} intrinsics + // require the element types of the vectors to be the same, we + // need to keep this around for bitcasts between VLAT <-> VLST where + // the element types of the vectors are not the same, until we figure + // out a better way of doing these casts. + assert(!UnimplementedFeature::cirVectorType()); + return CGF.getBuilder().createBitcast(CGF.getLoc(E->getSourceRange()), Src, + DstTy); + } + case CK_AddressSpaceConversion: + llvm_unreachable("NYI"); + case CK_AtomicToNonAtomic: + llvm_unreachable("NYI"); + case CK_NonAtomicToAtomic: + llvm_unreachable("NYI"); + case CK_UserDefinedConversion: + return Visit(const_cast(E)); + case CK_NoOp: { + auto V = Visit(const_cast(E)); + if (V) { + // CK_NoOp can model a pointer qualification conversion, which can remove + // an array bound and change the IR type. + // FIXME: Once pointee types are removed from IR, remove this. + auto T = CGF.convertType(DestTy); + if (T != V.getType()) + assert(0 && "NYI"); + } + return V; + } + case CK_BaseToDerived: + llvm_unreachable("NYI"); + case CK_DerivedToBase: { + // The EmitPointerWithAlignment path does this fine; just discard + // the alignment. + return CGF.buildPointerWithAlignment(CE).getPointer(); + } + case CK_Dynamic: + llvm_unreachable("NYI"); + case CK_ArrayToPointerDecay: + return CGF.buildArrayToPointerDecay(E).getPointer(); + case CK_FunctionToPointerDecay: + return buildLValue(E).getPointer(); + + case CK_NullToPointer: { + // FIXME: use MustVisitNullValue(E) and evaluate expr. + // Note that DestTy is used as the MLIR type instead of a custom + // nullptr type. + mlir::Type Ty = CGF.getCIRType(DestTy); + return Builder.create( + CGF.getLoc(E->getExprLoc()), Ty, + mlir::cir::ConstPtrAttr::get(Builder.getContext(), Ty, 0)); + } + + case CK_NullToMemberPointer: + llvm_unreachable("NYI"); + case CK_ReinterpretMemberPointer: + llvm_unreachable("NYI"); + case CK_BaseToDerivedMemberPointer: + llvm_unreachable("NYI"); + case CK_DerivedToBaseMemberPointer: + llvm_unreachable("NYI"); + case CK_ARCProduceObject: + llvm_unreachable("NYI"); + case CK_ARCConsumeObject: + llvm_unreachable("NYI"); + case CK_ARCReclaimReturnedObject: + llvm_unreachable("NYI"); + case CK_ARCExtendBlockObject: + llvm_unreachable("NYI"); + case CK_CopyAndAutoreleaseBlockObject: + llvm_unreachable("NYI"); + case CK_FloatingRealToComplex: + llvm_unreachable("NYI"); + case CK_FloatingComplexCast: + llvm_unreachable("NYI"); + case CK_IntegralComplexToFloatingComplex: + llvm_unreachable("NYI"); + case CK_FloatingComplexToIntegralComplex: + llvm_unreachable("NYI"); + case CK_ConstructorConversion: + llvm_unreachable("NYI"); + case CK_ToUnion: + llvm_unreachable("NYI"); + + case CK_LValueToRValue: + assert(CGF.getContext().hasSameUnqualifiedType(E->getType(), DestTy)); + assert(E->isGLValue() && "lvalue-to-rvalue applied to r-value!"); + return Visit(const_cast(E)); + + case CK_IntegralToPointer: { + auto DestCIRTy = ConvertType(DestTy); + mlir::Value Src = Visit(const_cast(E)); + + // Properly resize by casting to an int of the same size as the pointer. + auto MiddleTy = CGF.CGM.getDataLayout().getIntPtrType(DestCIRTy); + auto MiddleVal = Builder.createIntCast(Src, MiddleTy); + + if (CGF.CGM.getCodeGenOpts().StrictVTablePointers) + llvm_unreachable("NYI"); + + return Builder.createIntToPtr(MiddleVal, DestCIRTy); + } + case CK_PointerToIntegral: { + assert(!DestTy->isBooleanType() && "bool should use PointerToBool"); + if (CGF.CGM.getCodeGenOpts().StrictVTablePointers) + llvm_unreachable("NYI"); + return Builder.createPtrToInt(Visit(E), ConvertType(DestTy)); + } + case CK_ToVoid: { + CGF.buildIgnoredExpr(E); + return nullptr; + } + case CK_MatrixCast: + llvm_unreachable("NYI"); + case CK_VectorSplat: + llvm_unreachable("NYI"); + case CK_FixedPointCast: + llvm_unreachable("NYI"); + case CK_FixedPointToBoolean: + llvm_unreachable("NYI"); + case CK_FixedPointToIntegral: + llvm_unreachable("NYI"); + case CK_IntegralToFixedPoint: + llvm_unreachable("NYI"); + + case CK_IntegralCast: { + ScalarConversionOpts Opts; + if (auto *ICE = dyn_cast(CE)) { + if (!ICE->isPartOfExplicitCast()) + Opts = ScalarConversionOpts(CGF.SanOpts); + } + return buildScalarConversion(Visit(E), E->getType(), DestTy, + CE->getExprLoc(), Opts); + } + + case CK_IntegralToFloating: + case CK_FloatingToIntegral: + case CK_FloatingCast: + case CK_FixedPointToFloating: + case CK_FloatingToFixedPoint: { + if (Kind == CK_FixedPointToFloating || Kind == CK_FloatingToFixedPoint) + llvm_unreachable("Fixed point casts are NYI."); + CIRGenFunction::CIRGenFPOptionsRAII FPOptsRAII(CGF, CE); + return buildScalarConversion(Visit(E), E->getType(), DestTy, + CE->getExprLoc()); + } + case CK_BooleanToSignedIntegral: + llvm_unreachable("NYI"); + + case CK_IntegralToBoolean: { + return buildIntToBoolConversion(Visit(E), CGF.getLoc(CE->getSourceRange())); + } + + case CK_PointerToBoolean: + return buildPointerToBoolConversion(Visit(E), E->getType()); + case CK_FloatingToBoolean: + return buildFloatToBoolConversion(Visit(E), CGF.getLoc(E->getExprLoc())); + case CK_MemberPointerToBoolean: + llvm_unreachable("NYI"); + case CK_FloatingComplexToReal: + llvm_unreachable("NYI"); + case CK_IntegralComplexToReal: + llvm_unreachable("NYI"); + case CK_FloatingComplexToBoolean: + llvm_unreachable("NYI"); + case CK_IntegralComplexToBoolean: + llvm_unreachable("NYI"); + case CK_ZeroToOCLOpaqueType: + llvm_unreachable("NYI"); + case CK_IntToOCLSampler: + llvm_unreachable("NYI"); + + default: + emitError(CGF.getLoc(CE->getExprLoc()), "cast kind not implemented: '") + << CE->getCastKindName() << "'"; + return nullptr; + } // end of switch + + llvm_unreachable("unknown scalar cast"); +} + +mlir::Value ScalarExprEmitter::VisitCallExpr(const CallExpr *E) { + if (E->getCallReturnType(CGF.getContext())->isReferenceType()) + return buildLoadOfLValue(E); + + auto V = CGF.buildCallExpr(E).getScalarVal(); + assert(!UnimplementedFeature::buildLValueAlignmentAssumption()); + return V; +} + +mlir::Value ScalarExprEmitter::VisitMemberExpr(MemberExpr *E) { + // TODO(cir): Folding all this constants sound like work for MLIR optimizers, + // keep assertion for now. + assert(!UnimplementedFeature::tryEmitAsConstant()); + Expr::EvalResult Result; + if (E->EvaluateAsInt(Result, CGF.getContext(), Expr::SE_AllowSideEffects)) + assert(0 && "NYI"); + return buildLoadOfLValue(E); +} + +/// Emit a conversion from the specified type to the specified destination +/// type, both of which are CIR scalar types. +mlir::Value CIRGenFunction::buildScalarConversion(mlir::Value Src, + QualType SrcTy, + QualType DstTy, + SourceLocation Loc) { + assert(CIRGenFunction::hasScalarEvaluationKind(SrcTy) && + CIRGenFunction::hasScalarEvaluationKind(DstTy) && + "Invalid scalar expression to emit"); + return ScalarExprEmitter(*this, builder) + .buildScalarConversion(Src, SrcTy, DstTy, Loc); +} + +/// If the specified expression does not fold +/// to a constant, or if it does but contains a label, return false. If it +/// constant folds return true and set the boolean result in Result. +bool CIRGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, + bool &ResultBool, + bool AllowLabels) { + llvm::APSInt ResultInt; + if (!ConstantFoldsToSimpleInteger(Cond, ResultInt, AllowLabels)) + return false; + + ResultBool = ResultInt.getBoolValue(); + return true; +} + +mlir::Value ScalarExprEmitter::VisitInitListExpr(InitListExpr *E) { + bool Ignore = TestAndClearIgnoreResultAssign(); + (void)Ignore; + assert(Ignore == false && "init list ignored"); + unsigned NumInitElements = E->getNumInits(); + + if (E->hadArrayRangeDesignator()) + llvm_unreachable("NYI"); + + if (UnimplementedFeature::cirVectorType()) + llvm_unreachable("NYI"); + + if (NumInitElements == 0) { + // C++11 value-initialization for the scalar. + llvm_unreachable("NYI"); + } + + return Visit(E->getInit(0)); +} + +mlir::Value ScalarExprEmitter::VisitUnaryLNot(const UnaryOperator *E) { + // Perform vector logical not on comparison with zero vector. + if (E->getType()->isVectorType() && + E->getType()->castAs()->getVectorKind() == + VectorType::GenericVector) { + llvm_unreachable("NYI"); + } + + // Compare operand to zero. + mlir::Value boolVal = CGF.evaluateExprAsBool(E->getSubExpr()); + + // Invert value. + boolVal = Builder.createNot(boolVal); + + // ZExt result to the expr type. + auto dstTy = ConvertType(E->getType()); + if (dstTy.isa()) + return Builder.createBoolToInt(boolVal, dstTy); + if (dstTy.isa()) + return boolVal; + + llvm_unreachable("destination type for negation unary operator is NYI"); +} + +mlir::Value ScalarExprEmitter::buildScalarCast( + mlir::Value Src, QualType SrcType, QualType DstType, mlir::Type SrcTy, + mlir::Type DstTy, ScalarConversionOpts Opts) { + // The Element types determine the type of cast to perform. + mlir::Type SrcElementTy; + mlir::Type DstElementTy; + QualType SrcElementType; + QualType DstElementType; + if (SrcType->isMatrixType() && DstType->isMatrixType()) { + llvm_unreachable("NYI"); + } else { + assert(!SrcType->isMatrixType() && !DstType->isMatrixType() && + "cannot cast between matrix and non-matrix types"); + SrcElementTy = SrcTy; + DstElementTy = DstTy; + SrcElementType = SrcType; + DstElementType = DstType; + } + + if (SrcElementTy.isa() || + DstElementTy.isa()) + llvm_unreachable("Obsolete code. Don't use mlir::IntegerType with CIR."); + + if (SrcElementType->isBooleanType()) { + if (Opts.TreatBooleanAsSigned) + llvm_unreachable("NYI: signed bool"); + if (CGF.getBuilder().isInt(DstElementTy)) { + return Builder.create( + Src.getLoc(), DstTy, mlir::cir::CastKind::bool_to_int, Src); + } else if (DstTy.isa()) { + llvm_unreachable("NYI: bool->float cast"); + } else { + llvm_unreachable("Unexpected destination type for scalar cast"); + } + } else if (CGF.getBuilder().isInt(SrcElementTy)) { + if (CGF.getBuilder().isInt(DstElementTy)) { + return Builder.create( + Src.getLoc(), DstTy, mlir::cir::CastKind::integral, Src); + } else if (DstElementTy.isa()) { + return Builder.create( + Src.getLoc(), DstTy, mlir::cir::CastKind::int_to_float, Src); + } else { + llvm_unreachable("Unexpected destination type for scalar cast"); + } + } else if (SrcElementTy.isa()) { + if (CGF.getBuilder().isInt(DstElementTy)) { + // If we can't recognize overflow as undefined behavior, assume that + // overflow saturates. This protects against normal optimizations if we + // are compiling with non-standard FP semantics. + if (!CGF.CGM.getCodeGenOpts().StrictFloatCastOverflow) + llvm_unreachable("NYI"); + if (Builder.getIsFPConstrained()) + llvm_unreachable("NYI"); + return Builder.create( + Src.getLoc(), DstTy, mlir::cir::CastKind::float_to_int, Src); + } else if (DstElementTy.isa()) { + // TODO: split this to createFPExt/createFPTrunc + auto FloatDstTy = DstTy.cast(); + auto FloatSrcTy = SrcTy.cast(); + return Builder.createFloatingCast(Src, DstTy); + } else { + llvm_unreachable("Unexpected destination type for scalar cast"); + } + } else { + llvm_unreachable("Unexpected source type for scalar cast"); + } +} + +LValue +CIRGenFunction::buildCompoundAssignmentLValue(const CompoundAssignOperator *E) { + ScalarExprEmitter Scalar(*this, builder); + mlir::Value Result; + switch (E->getOpcode()) { +#define COMPOUND_OP(Op) \ + case BO_##Op##Assign: \ + return Scalar.buildCompoundAssignLValue(E, &ScalarExprEmitter::build##Op, \ + Result) + COMPOUND_OP(Mul); + COMPOUND_OP(Div); + COMPOUND_OP(Rem); + COMPOUND_OP(Add); + COMPOUND_OP(Sub); + COMPOUND_OP(Shl); + COMPOUND_OP(Shr); + COMPOUND_OP(And); + COMPOUND_OP(Xor); + COMPOUND_OP(Or); +#undef COMPOUND_OP + + case BO_PtrMemD: + case BO_PtrMemI: + case BO_Mul: + case BO_Div: + case BO_Rem: + case BO_Add: + case BO_Sub: + case BO_Shl: + case BO_Shr: + case BO_LT: + case BO_GT: + case BO_LE: + case BO_GE: + case BO_EQ: + case BO_NE: + case BO_Cmp: + case BO_And: + case BO_Xor: + case BO_Or: + case BO_LAnd: + case BO_LOr: + case BO_Assign: + case BO_Comma: + llvm_unreachable("Not valid compound assignment operators"); + } + llvm_unreachable("Unhandled compound assignment operator"); +} + +LValue ScalarExprEmitter::buildCompoundAssignLValue( + const CompoundAssignOperator *E, + mlir::Value (ScalarExprEmitter::*Func)(const BinOpInfo &), + mlir::Value &Result) { + QualType LHSTy = E->getLHS()->getType(); + BinOpInfo OpInfo; + + if (E->getComputationResultType()->isAnyComplexType()) + assert(0 && "not implemented"); + + // Emit the RHS first. __block variables need to have the rhs evaluated + // first, plus this should improve codegen a little. + OpInfo.RHS = Visit(E->getRHS()); + OpInfo.Ty = E->getComputationResultType(); + OpInfo.Opcode = E->getOpcode(); + OpInfo.FPFeatures = E->getFPFeaturesInEffect(CGF.getLangOpts()); + OpInfo.E = E; + OpInfo.Loc = E->getSourceRange(); + + // Load/convert the LHS + LValue LHSLV = CGF.buildLValue(E->getLHS()); + + if (const AtomicType *atomicTy = LHSTy->getAs()) { + assert(0 && "not implemented"); + } + + OpInfo.LHS = buildLoadOfLValue(LHSLV, E->getExprLoc()); + + CIRGenFunction::SourceLocRAIIObject sourceloc{ + CGF, CGF.getLoc(E->getSourceRange())}; + SourceLocation Loc = E->getExprLoc(); + OpInfo.LHS = + buildScalarConversion(OpInfo.LHS, LHSTy, E->getComputationLHSType(), Loc); + + // Expand the binary operator. + Result = (this->*Func)(OpInfo); + + // Convert the result back to the LHS type, + // potentially with Implicit Conversion sanitizer check. + Result = buildScalarConversion(Result, E->getComputationResultType(), LHSTy, + Loc, ScalarConversionOpts(CGF.SanOpts)); + + // Store the result value into the LHS lvalue. Bit-fields are handled + // specially because the result is altered by the store, i.e., [C99 6.5.16p1] + // 'An assignment expression has the value of the left operand after the + // assignment...'. + if (LHSLV.isBitField()) + assert(0 && "not yet implemented"); + else + CGF.buildStoreThroughLValue(RValue::get(Result), LHSLV); + + assert(!CGF.getLangOpts().OpenMP && "Not implemented"); + return LHSLV; +} + +mlir::Value ScalarExprEmitter::buildNullValue(QualType Ty, mlir::Location loc) { + return CGF.buildFromMemory(CGF.CGM.buildNullConstant(Ty, loc), Ty); +} + +mlir::Value ScalarExprEmitter::buildCompoundAssign( + const CompoundAssignOperator *E, + mlir::Value (ScalarExprEmitter::*Func)(const BinOpInfo &)) { + + bool Ignore = TestAndClearIgnoreResultAssign(); + mlir::Value RHS; + LValue LHS = buildCompoundAssignLValue(E, Func, RHS); + + // If the result is clearly ignored, return now. + if (Ignore) + return {}; + + // The result of an assignment in C is the assigned r-value. + if (!CGF.getLangOpts().CPlusPlus) + return RHS; + + // If the lvalue is non-volatile, return the computed value of the assignment. + if (!LHS.isVolatile()) + return RHS; + + // Otherwise, reload the value. + return buildLoadOfLValue(LHS, E->getExprLoc()); +} + +mlir::Value ScalarExprEmitter::VisitExprWithCleanups(ExprWithCleanups *E) { + auto scopeLoc = CGF.getLoc(E->getSourceRange()); + auto &builder = CGF.builder; + + auto scope = builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Type &yieldTy, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + loc, builder.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexScopeGuard{CGF, &lexScope}; + auto scopeYieldVal = Visit(E->getSubExpr()); + if (scopeYieldVal) { + builder.create(loc, scopeYieldVal); + yieldTy = scopeYieldVal.getType(); + } + }); + + // Defend against dominance problems caused by jumps out of expression + // evaluation through the shared cleanup block. + // TODO(cir): Scope.ForceCleanup({&V}); + return scope.getNumResults() > 0 ? scope->getResult(0) : nullptr; +} + +mlir::Value ScalarExprEmitter::VisitBinAssign(const BinaryOperator *E) { + bool Ignore = TestAndClearIgnoreResultAssign(); + + mlir::Value RHS; + LValue LHS; + + switch (E->getLHS()->getType().getObjCLifetime()) { + case Qualifiers::OCL_Strong: + llvm_unreachable("NYI"); + case Qualifiers::OCL_Autoreleasing: + llvm_unreachable("NYI"); + case Qualifiers::OCL_ExplicitNone: + llvm_unreachable("NYI"); + case Qualifiers::OCL_Weak: + llvm_unreachable("NYI"); + case Qualifiers::OCL_None: + // __block variables need to have the rhs evaluated first, plus this should + // improve codegen just a little. + RHS = Visit(E->getRHS()); + LHS = buildCheckedLValue(E->getLHS(), CIRGenFunction::TCK_Store); + + // Store the value into the LHS. Bit-fields are handled specially because + // the result is altered by the store, i.e., [C99 6.5.16p1] + // 'An assignment expression has the value of the left operand after the + // assignment...'. + if (LHS.isBitField()) { + CGF.buildStoreThroughBitfieldLValue(RValue::get(RHS), LHS, RHS); + } else { + CGF.buildNullabilityCheck(LHS, RHS, E->getExprLoc()); + CIRGenFunction::SourceLocRAIIObject loc{CGF, + CGF.getLoc(E->getSourceRange())}; + CGF.buildStoreThroughLValue(RValue::get(RHS), LHS); + } + } + + // If the result is clearly ignored, return now. + if (Ignore) + return nullptr; + + // The result of an assignment in C is the assigned r-value. + if (!CGF.getLangOpts().CPlusPlus) + return RHS; + + // If the lvalue is non-volatile, return the computed value of the assignment. + if (!LHS.isVolatileQualified()) + return RHS; + + // Otherwise, reload the value. + return buildLoadOfLValue(LHS, E->getExprLoc()); +} + +/// Return true if the specified expression is cheap enough and side-effect-free +/// enough to evaluate unconditionally instead of conditionally. This is used +/// to convert control flow into selects in some cases. +/// TODO(cir): can be shared with LLVM codegen. +static bool isCheapEnoughToEvaluateUnconditionally(const Expr *E, + CIRGenFunction &CGF) { + // Anything that is an integer or floating point constant is fine. + return E->IgnoreParens()->isEvaluatable(CGF.getContext()); + + // Even non-volatile automatic variables can't be evaluated unconditionally. + // Referencing a thread_local may cause non-trivial initialization work to + // occur. If we're inside a lambda and one of the variables is from the scope + // outside the lambda, that function may have returned already. Reading its + // locals is a bad idea. Also, these reads may introduce races there didn't + // exist in the source-level program. +} + +mlir::Value ScalarExprEmitter::VisitAbstractConditionalOperator( + const AbstractConditionalOperator *E) { + auto &builder = CGF.getBuilder(); + auto loc = CGF.getLoc(E->getSourceRange()); + TestAndClearIgnoreResultAssign(); + + // Bind the common expression if necessary. + CIRGenFunction::OpaqueValueMapping binding(CGF, E); + + Expr *condExpr = E->getCond(); + Expr *lhsExpr = E->getTrueExpr(); + Expr *rhsExpr = E->getFalseExpr(); + + // If the condition constant folds and can be elided, try to avoid emitting + // the condition and the dead arm. + bool CondExprBool; + if (CGF.ConstantFoldsToSimpleInteger(condExpr, CondExprBool)) { + Expr *live = lhsExpr, *dead = rhsExpr; + if (!CondExprBool) + std::swap(live, dead); + + // If the dead side doesn't have labels we need, just emit the Live part. + if (!CGF.ContainsLabel(dead)) { + if (CondExprBool) + assert(!UnimplementedFeature::incrementProfileCounter()); + auto Result = Visit(live); + + // If the live part is a throw expression, it acts like it has a void + // type, so evaluating it returns a null Value. However, a conditional + // with non-void type must return a non-null Value. + if (!Result && !E->getType()->isVoidType()) { + llvm_unreachable("NYI"); + } + + return Result; + } + } + + // OpenCL: If the condition is a vector, we can treat this condition like + // the select function. + if ((CGF.getLangOpts().OpenCL && condExpr->getType()->isVectorType()) || + condExpr->getType()->isExtVectorType()) { + llvm_unreachable("NYI"); + } + + if (condExpr->getType()->isVectorType() || + condExpr->getType()->isSveVLSBuiltinType()) { + llvm_unreachable("NYI"); + } + + // If this is a really simple expression (like x ? 4 : 5), emit this as a + // select instead of as control flow. We can only do this if it is cheap and + // safe to evaluate the LHS and RHS unconditionally. + if (isCheapEnoughToEvaluateUnconditionally(lhsExpr, CGF) && + isCheapEnoughToEvaluateUnconditionally(rhsExpr, CGF)) { + bool lhsIsVoid = false; + auto condV = CGF.evaluateExprAsBool(condExpr); + assert(!UnimplementedFeature::incrementProfileCounter()); + + return builder + .create( + loc, condV, /*thenBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + auto lhs = Visit(lhsExpr); + if (!lhs) { + lhs = builder.getNullValue(CGF.VoidTy, loc); + lhsIsVoid = true; + } + builder.create(loc, lhs); + }, + /*elseBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + auto rhs = Visit(rhsExpr); + if (lhsIsVoid) { + assert(!rhs && "lhs and rhs types must match"); + rhs = builder.getNullValue(CGF.VoidTy, loc); + } + builder.create(loc, rhs); + }) + .getResult(); + } + + mlir::Value condV = CGF.buildOpOnBoolExpr(condExpr, loc, lhsExpr, rhsExpr); + CIRGenFunction::ConditionalEvaluation eval(CGF); + SmallVector insertPoints{}; + mlir::Type yieldTy{}; + + auto patchVoidOrThrowSites = [&]() { + if (insertPoints.empty()) + return; + // If both arms are void, so be it. + if (!yieldTy) + yieldTy = CGF.VoidTy; + + // Insert required yields. + for (auto &toInsert : insertPoints) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.restoreInsertionPoint(toInsert); + + // Block does not return: build empty yield. + if (yieldTy.isa()) { + builder.create(loc); + } else { // Block returns: set null yield value. + mlir::Value op0 = builder.getNullValue(yieldTy, loc); + builder.create(loc, op0); + } + } + }; + + return builder + .create( + loc, condV, /*trueBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{loc, + b.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexThenGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + + assert(!UnimplementedFeature::incrementProfileCounter()); + eval.begin(CGF); + auto lhs = Visit(lhsExpr); + eval.end(CGF); + + if (lhs) { + yieldTy = lhs.getType(); + b.create(loc, lhs); + return; + } + // If LHS or RHS is a throw or void expression we need to patch arms + // as to properly match yield types. + insertPoints.push_back(b.saveInsertionPoint()); + }, + /*falseBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{loc, + b.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + + assert(!UnimplementedFeature::incrementProfileCounter()); + eval.begin(CGF); + auto rhs = Visit(rhsExpr); + eval.end(CGF); + + if (rhs) { + yieldTy = rhs.getType(); + b.create(loc, rhs); + } else { + // If LHS or RHS is a throw or void expression we need to patch + // arms as to properly match yield types. + insertPoints.push_back(b.saveInsertionPoint()); + } + + patchVoidOrThrowSites(); + }) + .getResult(); +} + +mlir::Value CIRGenFunction::buildScalarPrePostIncDec(const UnaryOperator *E, + LValue LV, bool isInc, + bool isPre) { + return ScalarExprEmitter(*this, builder) + .buildScalarPrePostIncDec(E, LV, isInc, isPre); +} + +mlir::Value ScalarExprEmitter::VisitBinLAnd(const clang::BinaryOperator *E) { + if (E->getType()->isVectorType()) { + llvm_unreachable("NYI"); + } + + bool InstrumentRegions = CGF.CGM.getCodeGenOpts().hasProfileClangInstr(); + mlir::Type ResTy = ConvertType(E->getType()); + mlir::Location Loc = CGF.getLoc(E->getExprLoc()); + + // If we have 0 && RHS, see if we can elide RHS, if so, just return 0. + // If we have 1 && X, just emit X without inserting the control flow. + bool LHSCondVal; + if (CGF.ConstantFoldsToSimpleInteger(E->getLHS(), LHSCondVal)) { + if (LHSCondVal) { // If we have 1 && X, just emit X. + + mlir::Value RHSCond = CGF.evaluateExprAsBool(E->getRHS()); + + if (InstrumentRegions) { + llvm_unreachable("NYI"); + } + // ZExt result to int or bool. + return Builder.createZExtOrBitCast(RHSCond.getLoc(), RHSCond, ResTy); + } + // 0 && RHS: If it is safe, just elide the RHS, and return 0/false. + if (!CGF.ContainsLabel(E->getRHS())) + return Builder.getBool(false, Loc); + } + + CIRGenFunction::ConditionalEvaluation eval(CGF); + + mlir::Value LHSCondV = CGF.evaluateExprAsBool(E->getLHS()); + auto ResOp = Builder.create( + Loc, LHSCondV, /*trueBuilder=*/ + [&](mlir::OpBuilder &B, mlir::Location Loc) { + CIRGenFunction::LexicalScopeContext LexScope{Loc, + B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &LexScope}; + CGF.currLexScope->setAsTernary(); + mlir::Value RHSCondV = CGF.evaluateExprAsBool(E->getRHS()); + auto res = B.create( + Loc, RHSCondV, /*trueBuilder*/ + [&](mlir::OpBuilder &B, mlir::Location Loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + Loc, B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + auto res = B.create( + Loc, Builder.getBoolTy(), + Builder.getAttr(Builder.getBoolTy(), + true)); + B.create(Loc, res.getRes()); + }, + /*falseBuilder*/ + [&](mlir::OpBuilder &b, mlir::Location Loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + Loc, b.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + auto res = b.create( + Loc, Builder.getBoolTy(), + Builder.getAttr(Builder.getBoolTy(), + false)); + b.create(Loc, res.getRes()); + }); + B.create(Loc, res.getResult()); + }, + /*falseBuilder*/ + [&](mlir::OpBuilder &B, mlir::Location Loc) { + CIRGenFunction::LexicalScopeContext lexScope{Loc, + B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + auto res = B.create( + Loc, Builder.getBoolTy(), + Builder.getAttr(Builder.getBoolTy(), false)); + B.create(Loc, res.getRes()); + }); + return Builder.createZExtOrBitCast(ResOp.getLoc(), ResOp.getResult(), ResTy); +} + +mlir::Value ScalarExprEmitter::VisitBinLOr(const clang::BinaryOperator *E) { + if (E->getType()->isVectorType()) { + llvm_unreachable("NYI"); + } + + bool InstrumentRegions = CGF.CGM.getCodeGenOpts().hasProfileClangInstr(); + mlir::Type ResTy = ConvertType(E->getType()); + mlir::Location Loc = CGF.getLoc(E->getExprLoc()); + + // If we have 1 || RHS, see if we can elide RHS, if so, just return 1. + // If we have 0 || X, just emit X without inserting the control flow. + bool LHSCondVal; + if (CGF.ConstantFoldsToSimpleInteger(E->getLHS(), LHSCondVal)) { + if (!LHSCondVal) { // If we have 0 || X, just emit X. + + mlir::Value RHSCond = CGF.evaluateExprAsBool(E->getRHS()); + + if (InstrumentRegions) { + llvm_unreachable("NYI"); + } + // ZExt result to int or bool. + return Builder.createZExtOrBitCast(RHSCond.getLoc(), RHSCond, ResTy); + } + // 1 || RHS: If it is safe, just elide the RHS, and return 1/true. + if (!CGF.ContainsLabel(E->getRHS())) + return Builder.getBool(true, Loc); + } + + CIRGenFunction::ConditionalEvaluation eval(CGF); + + mlir::Value LHSCondV = CGF.evaluateExprAsBool(E->getLHS()); + auto ResOp = Builder.create( + Loc, LHSCondV, /*trueBuilder=*/ + [&](mlir::OpBuilder &B, mlir::Location Loc) { + CIRGenFunction::LexicalScopeContext lexScope{Loc, + B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + auto res = B.create( + Loc, Builder.getBoolTy(), + Builder.getAttr(Builder.getBoolTy(), true)); + B.create(Loc, res.getRes()); + }, + /*falseBuilder*/ + [&](mlir::OpBuilder &B, mlir::Location Loc) { + CIRGenFunction::LexicalScopeContext LexScope{Loc, + B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &LexScope}; + CGF.currLexScope->setAsTernary(); + mlir::Value RHSCondV = CGF.evaluateExprAsBool(E->getRHS()); + auto res = B.create( + Loc, RHSCondV, /*trueBuilder*/ + [&](mlir::OpBuilder &B, mlir::Location Loc) { + SmallVector Locs; + if (Loc.isa()) { + Locs.push_back(Loc); + Locs.push_back(Loc); + } else if (Loc.isa()) { + auto fusedLoc = Loc.cast(); + Locs.push_back(fusedLoc.getLocations()[0]); + Locs.push_back(fusedLoc.getLocations()[1]); + } + CIRGenFunction::LexicalScopeContext lexScope{ + Loc, B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + auto res = B.create( + Loc, Builder.getBoolTy(), + Builder.getAttr(Builder.getBoolTy(), + true)); + B.create(Loc, res.getRes()); + }, + /*falseBuilder*/ + [&](mlir::OpBuilder &b, mlir::Location Loc) { + SmallVector Locs; + if (Loc.isa()) { + Locs.push_back(Loc); + Locs.push_back(Loc); + } else if (Loc.isa()) { + auto fusedLoc = Loc.cast(); + Locs.push_back(fusedLoc.getLocations()[0]); + Locs.push_back(fusedLoc.getLocations()[1]); + } + CIRGenFunction::LexicalScopeContext lexScope{ + Loc, B.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexElseGuard{CGF, &lexScope}; + CGF.currLexScope->setAsTernary(); + auto res = b.create( + Loc, Builder.getBoolTy(), + Builder.getAttr(Builder.getBoolTy(), + false)); + b.create(Loc, res.getRes()); + }); + B.create(Loc, res.getResult()); + }); + + return Builder.createZExtOrBitCast(ResOp.getLoc(), ResOp.getResult(), ResTy); +} + +mlir::Value ScalarExprEmitter::VisitVAArgExpr(VAArgExpr *VE) { + QualType Ty = VE->getType(); + + if (Ty->isVariablyModifiedType()) + assert(!UnimplementedFeature::variablyModifiedTypeEmission() && "NYI"); + + Address ArgValue = Address::invalid(); + mlir::Value Val = CGF.buildVAArg(VE, ArgValue); + + return Val; +} + +/// Return the size or alignment of the type of argument of the sizeof +/// expression as an integer. +mlir::Value ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr( + const UnaryExprOrTypeTraitExpr *E) { + QualType TypeToSize = E->getTypeOfArgument(); + if (E->getKind() == UETT_SizeOf) { + if (const VariableArrayType *VAT = + CGF.getContext().getAsVariableArrayType(TypeToSize)) { + llvm_unreachable("NYI"); + } + } else if (E->getKind() == UETT_OpenMPRequiredSimdAlign) { + llvm_unreachable("NYI"); + } + + // If this isn't sizeof(vla), the result must be constant; use the constant + // folding logic so we don't have to duplicate it here. + return Builder.getConstInt(CGF.getLoc(E->getSourceRange()), + E->EvaluateKnownConstInt(CGF.getContext())); +} + +mlir::Value CIRGenFunction::buildCheckedInBoundsGEP( + mlir::Type ElemTy, mlir::Value Ptr, ArrayRef IdxList, + bool SignedIndices, bool IsSubtraction, SourceLocation Loc) { + mlir::Type PtrTy = Ptr.getType(); + assert(IdxList.size() == 1 && "multi-index ptr arithmetic NYI"); + mlir::Value GEPVal = builder.create( + CGM.getLoc(Loc), PtrTy, Ptr, IdxList[0]); + + // If the pointer overflow sanitizer isn't enabled, do nothing. + if (!SanOpts.has(SanitizerKind::PointerOverflow)) + return GEPVal; + + // TODO(cir): the unreachable code below hides a substantial amount of code + // from the original codegen related with pointer overflow sanitizer. + assert(UnimplementedFeature::pointerOverflowSanitizer()); + llvm_unreachable("pointer overflow sanitizer NYI"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp new file mode 100644 index 000000000000..2a17bf7feb7d --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -0,0 +1,1383 @@ +//===- CIRGenFunction.cpp - Emit CIR from ASTs for a Function -------------===// +// +// 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 coordinates the per-function state used while generating code +// +//===----------------------------------------------------------------------===// + +#include "CIRGenFunction.h" +#include "CIRGenCXXABI.h" +#include "CIRGenModule.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/ASTLambda.h" +#include "clang/AST/ExprObjC.h" +#include "clang/Basic/Builtins.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/FPEnv.h" +#include "clang/Frontend/FrontendDiagnostic.h" + +#include "mlir/Dialect/Func/IR/FuncOps.h" + +using namespace cir; +using namespace clang; +using namespace mlir::cir; + +CIRGenFunction::CIRGenFunction(CIRGenModule &CGM, CIRGenBuilderTy &builder, + bool suppressNewContext) + : CIRGenTypeCache(CGM), CGM{CGM}, builder(builder), + SanOpts(CGM.getLangOpts().Sanitize), CurFPFeatures(CGM.getLangOpts()), + ShouldEmitLifetimeMarkers(false) { + if (!suppressNewContext) + CGM.getCXXABI().getMangleContext().startNewFunction(); + EHStack.setCGF(this); + + // TODO(CIR): SetFastMathFlags(CurFPFeatures); +} + +clang::ASTContext &CIRGenFunction::getContext() const { + return CGM.getASTContext(); +} + +mlir::Type CIRGenFunction::ConvertType(QualType T) { + return CGM.getTypes().ConvertType(T); +} + +TypeEvaluationKind CIRGenFunction::getEvaluationKind(QualType type) { + type = type.getCanonicalType(); + while (true) { + switch (type->getTypeClass()) { +#define TYPE(name, parent) +#define ABSTRACT_TYPE(name, parent) +#define NON_CANONICAL_TYPE(name, parent) case Type::name: +#define DEPENDENT_TYPE(name, parent) case Type::name: +#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(name, parent) case Type::name: +#include "clang/AST/TypeNodes.inc" + llvm_unreachable("non-canonical or dependent type in IR-generation"); + + case Type::Auto: + case Type::DeducedTemplateSpecialization: + llvm_unreachable("undeduced type in IR-generation"); + + // Various scalar types. + case Type::Builtin: + case Type::Pointer: + case Type::BlockPointer: + case Type::LValueReference: + case Type::RValueReference: + case Type::MemberPointer: + case Type::Vector: + case Type::ExtVector: + case Type::ConstantMatrix: + case Type::FunctionProto: + case Type::FunctionNoProto: + case Type::Enum: + case Type::ObjCObjectPointer: + case Type::Pipe: + case Type::BitInt: + return TEK_Scalar; + + // Complexes. + case Type::Complex: + return TEK_Complex; + + // Arrays, records, and Objective-C objects. + case Type::ConstantArray: + case Type::IncompleteArray: + case Type::VariableArray: + case Type::Record: + case Type::ObjCObject: + case Type::ObjCInterface: + return TEK_Aggregate; + + // We operate on atomic values according to their underlying type. + case Type::Atomic: + type = cast(type)->getValueType(); + continue; + } + llvm_unreachable("unknown type kind!"); + } +} + +mlir::Type CIRGenFunction::convertTypeForMem(QualType T) { + return CGM.getTypes().convertTypeForMem(T); +} + +mlir::Type CIRGenFunction::convertType(QualType T) { + return CGM.getTypes().ConvertType(T); +} + +mlir::Location CIRGenFunction::getLoc(SourceLocation SLoc) { + // Some AST nodes might contain invalid source locations (e.g. + // CXXDefaultArgExpr), workaround that to still get something out. + if (SLoc.isValid()) { + const SourceManager &SM = getContext().getSourceManager(); + PresumedLoc PLoc = SM.getPresumedLoc(SLoc); + StringRef Filename = PLoc.getFilename(); + return mlir::FileLineColLoc::get(builder.getStringAttr(Filename), + PLoc.getLine(), PLoc.getColumn()); + } else { + // Do our best... + assert(currSrcLoc && "expected to inherit some source location"); + return *currSrcLoc; + } +} + +mlir::Location CIRGenFunction::getLoc(SourceRange SLoc) { + // Some AST nodes might contain invalid source locations (e.g. + // CXXDefaultArgExpr), workaround that to still get something out. + if (SLoc.isValid()) { + mlir::Location B = getLoc(SLoc.getBegin()); + mlir::Location E = getLoc(SLoc.getEnd()); + SmallVector locs = {B, E}; + mlir::Attribute metadata; + return mlir::FusedLoc::get(locs, metadata, builder.getContext()); + } else if (currSrcLoc) { + return *currSrcLoc; + } + + // We're brave, but time to give up. + return builder.getUnknownLoc(); +} + +mlir::Location CIRGenFunction::getLoc(mlir::Location lhs, mlir::Location rhs) { + SmallVector locs = {lhs, rhs}; + mlir::Attribute metadata; + return mlir::FusedLoc::get(locs, metadata, builder.getContext()); +} + +/// Return true if the statement contains a label in it. If +/// this statement is not executed normally, it not containing a label means +/// that we can just remove the code. +bool CIRGenFunction::ContainsLabel(const Stmt *S, bool IgnoreCaseStmts) { + // Null statement, not a label! + if (!S) + return false; + + // If this is a label, we have to emit the code, consider something like: + // if (0) { ... foo: bar(); } goto foo; + // + // TODO: If anyone cared, we could track __label__'s, since we know that you + // can't jump to one from outside their declared region. + if (isa(S)) + return true; + + // If this is a case/default statement, and we haven't seen a switch, we + // have to emit the code. + if (isa(S) && !IgnoreCaseStmts) + return true; + + // If this is a switch statement, we want to ignore cases below it. + if (isa(S)) + IgnoreCaseStmts = true; + + // Scan subexpressions for verboten labels. + for (const Stmt *SubStmt : S->children()) + if (ContainsLabel(SubStmt, IgnoreCaseStmts)) + return true; + + return false; +} + +bool CIRGenFunction::sanitizePerformTypeCheck() const { + return SanOpts.has(SanitizerKind::Null) || + SanOpts.has(SanitizerKind::Alignment) || + SanOpts.has(SanitizerKind::ObjectSize) || + SanOpts.has(SanitizerKind::Vptr); +} + +void CIRGenFunction::buildTypeCheck(TypeCheckKind TCK, + clang::SourceLocation Loc, mlir::Value V, + clang::QualType Type, + clang::CharUnits Alignment, + clang::SanitizerSet SkippedChecks, + std::optional ArraySize) { + if (!sanitizePerformTypeCheck()) + return; + + assert(false && "type check NYI"); +} + +/// If the specified expression does not fold +/// to a constant, or if it does but contains a label, return false. If it +/// constant folds return true and set the folded value. +bool CIRGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, + llvm::APSInt &ResultInt, + bool AllowLabels) { + // FIXME: Rename and handle conversion of other evaluatable things + // to bool. + Expr::EvalResult Result; + if (!Cond->EvaluateAsInt(Result, getContext())) + return false; // Not foldable, not integer or not fully evaluatable. + + llvm::APSInt Int = Result.Val.getInt(); + if (!AllowLabels && ContainsLabel(Cond)) + return false; // Contains a label. + + ResultInt = Int; + return true; +} + +mlir::Type CIRGenFunction::getCIRType(const QualType &type) { + return CGM.getCIRType(type); +} + +/// Determine whether the function F ends with a return stmt. +static bool endsWithReturn(const Decl *F) { + const Stmt *Body = nullptr; + if (auto *FD = dyn_cast_or_null(F)) + Body = FD->getBody(); + else if (auto *OMD = dyn_cast_or_null(F)) + llvm_unreachable("NYI"); + + if (auto *CS = dyn_cast_or_null(Body)) { + auto LastStmt = CS->body_rbegin(); + if (LastStmt != CS->body_rend()) + return isa(*LastStmt); + } + return false; +} + +void CIRGenFunction::buildAndUpdateRetAlloca(QualType ty, mlir::Location loc, + CharUnits alignment) { + + if (ty->isVoidType()) { + // Void type; nothing to return. + ReturnValue = Address::invalid(); + + // Count the implicit return. + if (!endsWithReturn(CurFuncDecl)) + ++NumReturnExprs; + } else if (CurFnInfo->getReturnInfo().getKind() == ABIArgInfo::Indirect) { + // TODO(CIR): Consider this implementation in CIRtoLLVM + llvm_unreachable("NYI"); + // TODO(CIR): Consider this implementation in CIRtoLLVM + } else if (CurFnInfo->getReturnInfo().getKind() == ABIArgInfo::InAlloca) { + llvm_unreachable("NYI"); + } else { + auto addr = buildAlloca("__retval", ty, loc, alignment); + FnRetAlloca = addr; + ReturnValue = Address(addr, alignment); + + // Tell the epilog emitter to autorelease the result. We do this now so + // that various specialized functions can suppress it during their IR - + // generation + if (getLangOpts().ObjCAutoRefCount) + llvm_unreachable("NYI"); + } +} + +mlir::LogicalResult CIRGenFunction::declare(const Decl *var, QualType ty, + mlir::Location loc, + CharUnits alignment, + mlir::Value &addr, bool isParam) { + const auto *namedVar = dyn_cast_or_null(var); + assert(namedVar && "Needs a named decl"); + assert(!symbolTable.count(var) && "not supposed to be available just yet"); + + addr = buildAlloca(namedVar->getName(), ty, loc, alignment); + if (isParam) { + auto allocaOp = cast(addr.getDefiningOp()); + allocaOp.setInitAttr(mlir::UnitAttr::get(builder.getContext())); + } + + symbolTable.insert(var, addr); + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::declare(Address addr, const Decl *var, + QualType ty, mlir::Location loc, + CharUnits alignment, + mlir::Value &addrVal, + bool isParam) { + const auto *namedVar = dyn_cast_or_null(var); + assert(namedVar && "Needs a named decl"); + assert(!symbolTable.count(var) && "not supposed to be available just yet"); + + addrVal = addr.getPointer(); + if (isParam) { + auto allocaOp = cast(addrVal.getDefiningOp()); + allocaOp.setInitAttr(mlir::UnitAttr::get(builder.getContext())); + } + + symbolTable.insert(var, addrVal); + return mlir::success(); +} + +/// All scope related cleanup needed: +/// - Patching up unsolved goto's. +/// - Build all cleanup code and insert yield/returns. +void CIRGenFunction::LexicalScopeGuard::cleanup() { + auto &builder = CGF.builder; + auto *localScope = CGF.currLexScope; + + auto buildReturn = [&](mlir::Location loc) { + // If we are on a coroutine, add the coro_end builtin call. + auto Fn = dyn_cast(CGF.CurFn); + assert(Fn && "other callables NYI"); + if (Fn.getCoroutine()) + CGF.buildCoroEndBuiltinCall( + loc, builder.getNullPtr(builder.getVoidPtrTy(), loc)); + + if (CGF.FnRetCIRTy.has_value()) { + // If there's anything to return, load it first. + auto val = builder.create(loc, *CGF.FnRetCIRTy, *CGF.FnRetAlloca); + return builder.create(loc, llvm::ArrayRef(val.getResult())); + } + return builder.create(loc); + }; + + // Handle pending gotos and the solved labels in this scope. + while (!localScope->PendingGotos.empty()) { + auto gotoInfo = localScope->PendingGotos.back(); + // FIXME: Currently only support resolving goto labels inside the + // same lexical ecope. + assert(localScope->SolvedLabels.count(gotoInfo.second) && + "goto across scopes not yet supported"); + + // The goto in this lexical context actually maps to a basic + // block. + auto g = cast(gotoInfo.first); + g.setSuccessor(CGF.LabelMap[gotoInfo.second].getBlock()); + localScope->PendingGotos.pop_back(); + } + localScope->SolvedLabels.clear(); + + // Cleanup are done right before codegen resume a scope. This is where + // objects are destroyed. + unsigned curLoc = 0; + for (auto *retBlock : localScope->getRetBlocks()) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(retBlock); + mlir::Location retLoc = *localScope->getRetLocs()[curLoc]; + curLoc++; + + // TODO(cir): insert actual scope cleanup HERE (dtors and etc) + + (void)buildReturn(retLoc); + } + + auto insertCleanupAndLeave = [&](mlir::Block *InsPt) { + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(InsPt); + // TODO: insert actual scope cleanup (dtors and etc) + if (localScope->Depth != 0) { // end of any local scope != function + // Ternary ops have to deal with matching arms for yielding types + // and do return a value, it must do its own cir.yield insertion. + if (!localScope->isTernary()) + builder.create(localScope->EndLoc); + } else + (void)buildReturn(localScope->EndLoc); + }; + + // If a cleanup block has been created at some point, branch to it + // and set the insertion point to continue at the cleanup block. + // Terminators are then inserted either in the cleanup block or + // inline in this current block. + auto *cleanupBlock = localScope->getCleanupBlock(builder); + if (cleanupBlock) + insertCleanupAndLeave(cleanupBlock); + + // Now deal with any pending block wrap up like implicit end of + // scope. + + // If a terminator is already present in the current block, nothing + // else to do here. + bool entryBlock = builder.getInsertionBlock()->isEntryBlock(); + auto *currBlock = builder.getBlock(); + bool hasTerminator = + !currBlock->empty() && + currBlock->back().hasTrait(); + if (hasTerminator) + return; + + // An empty non-entry block has nothing to offer. + if (!entryBlock && currBlock->empty()) { + currBlock->erase(); + // Remove unused cleanup blocks. + if (cleanupBlock && cleanupBlock->hasNoPredecessors()) + cleanupBlock->erase(); + return; + } + + // If there's a cleanup block, branch to it, nothing else to do. + if (cleanupBlock) { + builder.create(currBlock->back().getLoc(), cleanupBlock); + return; + } + + // No pre-existent cleanup block, emit cleanup code and yield/return. + insertCleanupAndLeave(currBlock); +} + +mlir::cir::FuncOp +CIRGenFunction::generateCode(clang::GlobalDecl GD, mlir::cir::FuncOp Fn, + const CIRGenFunctionInfo &FnInfo) { + assert(Fn && "generating code for a null function"); + const auto FD = cast(GD.getDecl()); + CurGD = GD; + + FnRetQualTy = FD->getReturnType(); + if (!FnRetQualTy->isVoidType()) + FnRetCIRTy = getCIRType(FnRetQualTy); + + FunctionArgList Args; + QualType ResTy = buildFunctionArgList(GD, Args); + + if (FD->isInlineBuiltinDeclaration()) { + llvm_unreachable("NYI"); + } else { + // Detect the unusual situation where an inline version is shadowed by a + // non-inline version. In that case we should pick the external one + // everywhere. That's GCC behavior too. Unfortunately, I cannot find a way + // to detect that situation before we reach codegen, so do some late + // replacement. + for (const auto *PD = FD->getPreviousDecl(); PD; + PD = PD->getPreviousDecl()) { + if (LLVM_UNLIKELY(PD->isInlineBuiltinDeclaration())) { + llvm_unreachable("NYI"); + } + } + } + + // Check if we should generate debug info for this function. + if (FD->hasAttr()) { + llvm_unreachable("NYI"); + } + + // The function might not have a body if we're generating thunks for a + // function declaration. + SourceRange BodyRange; + if (Stmt *Body = FD->getBody()) + BodyRange = Body->getSourceRange(); + else + BodyRange = FD->getLocation(); + // TODO: CurEHLocation + + // Use the location of the start of the function to determine where the + // function definition is located. By default we use the location of the + // declaration as the location for the subprogram. A function may lack a + // declaration in the source code if it is created by code gen. (examples: + // _GLOBAL__I_a, __cxx_global_array_dtor, thunk). + SourceLocation Loc = FD->getLocation(); + + // If this is a function specialization then use the pattern body as the + // location for the function. + if (const auto *SpecDecl = FD->getTemplateInstantiationPattern()) + if (SpecDecl->hasBody(SpecDecl)) + Loc = SpecDecl->getLocation(); + + Stmt *Body = FD->getBody(); + + if (Body) { + // LLVM codegen: Coroutines always emit lifetime markers + // Hide this under request for lifetime emission so that we can write + // tests when the time comes, but CIR should be intrinsically scope + // accurate, so no need to tie coroutines to such markers. + if (isa(Body)) + assert(!UnimplementedFeature::shouldEmitLifetimeMarkers() && "NYI"); + + // Initialize helper which will detect jumps which can cause invalid + // lifetime markers. + if (ShouldEmitLifetimeMarkers) + assert(!UnimplementedFeature::shouldEmitLifetimeMarkers() && "NYI"); + } + + // Create a scope in the symbol table to hold variable declarations. + SymTableScopeTy varScope(symbolTable); + + { + // Compiler synthetized functions might have invalid slocs... + auto bSrcLoc = FD->getBody()->getBeginLoc(); + auto eSrcLoc = FD->getBody()->getEndLoc(); + auto unknownLoc = builder.getUnknownLoc(); + + auto FnBeginLoc = bSrcLoc.isValid() ? getLoc(bSrcLoc) : unknownLoc; + auto FnEndLoc = eSrcLoc.isValid() ? getLoc(eSrcLoc) : unknownLoc; + SourceLocRAIIObject fnLoc{*this, Loc.isValid() ? getLoc(Loc) : unknownLoc}; + + assert(Fn.isDeclaration() && "Function already has body?"); + mlir::Block *EntryBB = Fn.addEntryBlock(); + builder.setInsertionPointToStart(EntryBB); + + const auto fusedLoc = + mlir::FusedLoc::get(builder.getContext(), {FnBeginLoc, FnEndLoc}); + LexicalScopeContext lexScope{fusedLoc, EntryBB}; + LexicalScopeGuard scopeGuard{*this, &lexScope}; + + // Emit the standard function prologue. + StartFunction(GD, ResTy, Fn, FnInfo, Args, Loc, BodyRange.getBegin()); + + // Initialize lexical scope information. + + // Save parameters for coroutine function. + if (Body && isa_and_nonnull(Body)) + llvm::append_range(FnArgs, FD->parameters()); + + // Generate the body of the function. + // TODO: PGO.assignRegionCounters + if (isa(FD)) + buildDestructorBody(Args); + else if (isa(FD)) + buildConstructorBody(Args); + else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice && + FD->hasAttr()) + llvm_unreachable("NYI"); + else if (isa(FD) && + cast(FD)->isLambdaStaticInvoker()) { + // The lambda static invoker function is special, because it forwards or + // clones the body of the function call operator (but is actually static). + buildLambdaStaticInvokeBody(cast(FD)); + } else if (FD->isDefaulted() && isa(FD) && + (cast(FD)->isCopyAssignmentOperator() || + cast(FD)->isMoveAssignmentOperator())) { + // Implicit copy-assignment gets the same special treatment as implicit + // copy-constructors. + buildImplicitAssignmentOperatorBody(Args); + } else if (Body) { + if (mlir::failed(buildFunctionBody(Body))) { + Fn.erase(); + return nullptr; + } + } else + llvm_unreachable("no definition for emitted function"); + + assert(builder.getInsertionBlock() && "Should be valid"); + } + + if (mlir::failed(Fn.verifyBody())) + return nullptr; + + // C++11 [stmt.return]p2: + // Flowing off the end of a function [...] results in undefined behavior + // in a value-returning function. + // C11 6.9.1p12: + // If the '}' that terminates a function is reached, and the value of the + // function call is used by the caller, the behavior is undefined. + if (getLangOpts().CPlusPlus && !FD->hasImplicitReturnZero() && !SawAsmBlock && + !FD->getReturnType()->isVoidType() && builder.getInsertionBlock()) { + bool shouldEmitUnreachable = + CGM.getCodeGenOpts().StrictReturn || + !CGM.MayDropFunctionReturn(FD->getASTContext(), FD->getReturnType()); + + if (SanOpts.has(SanitizerKind::Return)) { + llvm_unreachable("NYI"); + } else if (shouldEmitUnreachable) { + if (CGM.getCodeGenOpts().OptimizationLevel == 0) + ; // TODO: buildTrapCall(llvm::Intrinsic::trap); + } + if (SanOpts.has(SanitizerKind::Return) || shouldEmitUnreachable) { + // TODO: builder.createUnreachable(); + builder.clearInsertionPoint(); + } + } + + // Emit the standard function epilogue. + // TODO: finishFunction(BodyRange.getEnd()); + + // If we haven't marked the function nothrow through other means, do a quick + // pass now to see if we can. + // TODO: if (!CurFn->doesNotThrow()) TryMarkNoThrow(CurFn); + + return Fn; +} + +mlir::Value CIRGenFunction::createLoad(const VarDecl *VD, const char *Name) { + auto addr = GetAddrOfLocalVar(VD); + return builder.create(getLoc(VD->getLocation()), + addr.getElementType(), addr.getPointer()); +} + +static bool isMemcpyEquivalentSpecialMember(const CXXMethodDecl *D) { + auto *CD = llvm::dyn_cast(D); + if (!(CD && CD->isCopyOrMoveConstructor()) && + !D->isCopyAssignmentOperator() && !D->isMoveAssignmentOperator()) + return false; + + // We can emit a memcpy for a trivial copy or move constructor/assignment + if (D->isTrivial() && !D->getParent()->mayInsertExtraPadding()) + return true; + + if (D->getParent()->isUnion() && D->isDefaulted()) + return true; + + return false; +} + +void CIRGenFunction::buildCXXConstructorCall(const clang::CXXConstructorDecl *D, + clang::CXXCtorType Type, + bool ForVirtualBase, + bool Delegating, + AggValueSlot ThisAVS, + const clang::CXXConstructExpr *E) { + CallArgList Args; + Address This = ThisAVS.getAddress(); + LangAS SlotAS = ThisAVS.getQualifiers().getAddressSpace(); + QualType ThisType = D->getThisType(); + LangAS ThisAS = ThisType.getTypePtr()->getPointeeType().getAddressSpace(); + mlir::Value ThisPtr = This.getPointer(); + + assert(SlotAS == ThisAS && "This edge case NYI"); + + Args.add(RValue::get(ThisPtr), D->getThisType()); + + // In LLVM Codegen: If this is a trivial constructor, just emit what's needed. + // If this is a union copy constructor, we must emit a memcpy, because the AST + // does not model that copy. + if (isMemcpyEquivalentSpecialMember(D)) { + assert(!UnimplementedFeature::isMemcpyEquivalentSpecialMember()); + } + + const FunctionProtoType *FPT = D->getType()->castAs(); + EvaluationOrder Order = E->isListInitialization() + ? EvaluationOrder::ForceLeftToRight + : EvaluationOrder::Default; + + buildCallArgs(Args, FPT, E->arguments(), E->getConstructor(), + /*ParamsToSkip*/ 0, Order); + + buildCXXConstructorCall(D, Type, ForVirtualBase, Delegating, This, Args, + ThisAVS.mayOverlap(), E->getExprLoc(), + ThisAVS.isSanitizerChecked()); +} + +void CIRGenFunction::buildCXXConstructorCall( + const CXXConstructorDecl *D, CXXCtorType Type, bool ForVirtualBase, + bool Delegating, Address This, CallArgList &Args, + AggValueSlot::Overlap_t Overlap, SourceLocation Loc, + bool NewPointerIsChecked) { + + const auto *ClassDecl = D->getParent(); + + if (!NewPointerIsChecked) + buildTypeCheck(CIRGenFunction::TCK_ConstructorCall, Loc, This.getPointer(), + getContext().getRecordType(ClassDecl), CharUnits::Zero()); + + // If this is a call to a trivial default constructor: + // In LLVM: do nothing. + // In CIR: emit as a regular call, other later passes should lower the + // ctor call into trivial initialization. + assert(!UnimplementedFeature::isTrivialAndisDefaultConstructor()); + + if (isMemcpyEquivalentSpecialMember(D)) { + assert(!UnimplementedFeature::isMemcpyEquivalentSpecialMember()); + } + + bool PassPrototypeArgs = true; + + assert(!D->getInheritedConstructor() && "inheritance NYI"); + + // Insert any ABI-specific implicit constructor arguments. + CIRGenCXXABI::AddedStructorArgCounts ExtraArgs = + CGM.getCXXABI().addImplicitConstructorArgs(*this, D, Type, ForVirtualBase, + Delegating, Args); + + // Emit the call. + auto CalleePtr = CGM.getAddrOfCXXStructor(GlobalDecl(D, Type)); + const CIRGenFunctionInfo &Info = CGM.getTypes().arrangeCXXConstructorCall( + Args, D, Type, ExtraArgs.Prefix, ExtraArgs.Suffix, PassPrototypeArgs); + CIRGenCallee Callee = CIRGenCallee::forDirect(CalleePtr, GlobalDecl(D, Type)); + mlir::cir::CallOp C; + buildCall(Info, Callee, ReturnValueSlot(), Args, &C, false, getLoc(Loc)); + + assert(CGM.getCodeGenOpts().OptimizationLevel == 0 || + ClassDecl->isDynamicClass() || Type == Ctor_Base || + !CGM.getCodeGenOpts().StrictVTablePointers && + "vtable assumption loads NYI"); +} + +void CIRGenFunction::buildConstructorBody(FunctionArgList &Args) { + // TODO: EmitAsanPrologueOrEpilogue(true); + const auto *Ctor = cast(CurGD.getDecl()); + auto CtorType = CurGD.getCtorType(); + + assert((CGM.getTarget().getCXXABI().hasConstructorVariants() || + CtorType == Ctor_Complete) && + "can only generate complete ctor for this ABI"); + + // Before we go any further, try the complete->base constructor delegation + // optimization. + if (CtorType == Ctor_Complete && IsConstructorDelegationValid(Ctor) && + CGM.getTarget().getCXXABI().hasConstructorVariants()) { + buildDelegateCXXConstructorCall(Ctor, Ctor_Base, Args, Ctor->getEndLoc()); + return; + } + + const FunctionDecl *Definition = nullptr; + Stmt *Body = Ctor->getBody(Definition); + assert(Definition == Ctor && "emitting wrong constructor body"); + + // Enter the function-try-block before the constructor prologue if + // applicable. + bool IsTryBody = (Body && isa(Body)); + if (IsTryBody) + llvm_unreachable("NYI"); + + // TODO: incrementProfileCounter + + // TODO: RunClenaupCcope RunCleanups(*this); + + // TODO: in restricted cases, we can emit the vbase initializers of a + // complete ctor and then delegate to the base ctor. + + // Emit the constructor prologue, i.e. the base and member initializers. + buildCtorPrologue(Ctor, CtorType, Args); + + // Emit the body of the statement. + if (IsTryBody) + llvm_unreachable("NYI"); + else { + // TODO: propagate this result via mlir::logical result. Just unreachable + // now just to have it handled. + if (mlir::failed(buildStmt(Body, true))) + llvm_unreachable("NYI"); + } + + // Emit any cleanup blocks associated with the member or base initializers, + // which inlcudes (along the exceptional path) the destructors for those + // members and bases that were fully constructed. + /// TODO: RunCleanups.ForceCleanup(); + + if (IsTryBody) + llvm_unreachable("NYI"); +} + +/// Given a value of type T* that may not be to a complete object, construct +/// an l-vlaue withi the natural pointee alignment of T. +LValue CIRGenFunction::MakeNaturalAlignPointeeAddrLValue(mlir::Value V, + QualType T) { + // FIXME(cir): is it safe to assume Op->getResult(0) is valid? Perhaps + // assert on the result type first. + LValueBaseInfo BaseInfo; + CharUnits Align = CGM.getNaturalTypeAlignment(T, &BaseInfo, + /* for PointeeType= */ true); + return makeAddrLValue(Address(V, Align), T, BaseInfo); +} + +LValue CIRGenFunction::MakeNaturalAlignAddrLValue(mlir::Value V, QualType T) { + LValueBaseInfo BaseInfo; + assert(!UnimplementedFeature::tbaa()); + CharUnits Alignment = CGM.getNaturalTypeAlignment(T, &BaseInfo); + Address Addr(V, getTypes().convertTypeForMem(T), Alignment); + return LValue::makeAddr(Addr, T, getContext(), BaseInfo); +} + +// Map the LangOption for exception behavior into the corresponding enum in +// the IR. +cir::fp::ExceptionBehavior +ToConstrainedExceptMD(LangOptions::FPExceptionModeKind Kind) { + switch (Kind) { + case LangOptions::FPE_Ignore: + return cir::fp::ebIgnore; + case LangOptions::FPE_MayTrap: + return cir::fp::ebMayTrap; + case LangOptions::FPE_Strict: + return cir::fp::ebStrict; + default: + llvm_unreachable("Unsupported FP Exception Behavior"); + } +} + +void CIRGenFunction::StartFunction(GlobalDecl GD, QualType RetTy, + mlir::cir::FuncOp Fn, + const CIRGenFunctionInfo &FnInfo, + const FunctionArgList &Args, + SourceLocation Loc, + SourceLocation StartLoc) { + assert(!CurFn && + "Do not use a CIRGenFunction object for more than one function"); + + const auto *D = GD.getDecl(); + + DidCallStackSave = false; + CurCodeDecl = D; + const auto *FD = dyn_cast_or_null(D); + if (FD && FD->usesSEHTry()) + CurSEHParent = GD; + CurFuncDecl = (D ? D->getNonClosureContext() : nullptr); + FnRetTy = RetTy; + CurFn = Fn; + CurFnInfo = &FnInfo; + + // If this function is ignored for any of the enabled sanitizers, disable + // the sanitizer for the function. + do { +#define SANITIZER(NAME, ID) \ + if (SanOpts.empty()) \ + break; \ + if (SanOpts.has(SanitizerKind::ID)) \ + if (CGM.isInNoSanitizeList(SanitizerKind::ID, Fn, Loc)) \ + SanOpts.set(SanitizerKind::ID, false); + +#include "clang/Basic/Sanitizers.def" +#undef SANITIZER + } while (0); + + if (D) { + bool NoSanitizeCoverage = false; + (void)NoSanitizeCoverage; + + for (auto Attr : D->specific_attrs()) { + (void)Attr; + llvm_unreachable("NYI"); + } + + // SanitizeCoverage is not handled by SanOpts + if (NoSanitizeCoverage && CGM.getCodeGenOpts().hasSanitizeCoverage()) + llvm_unreachable("NYI"); + } + + // Apply sanitizer attributes to the function. + if (SanOpts.hasOneOf(SanitizerKind::Address | SanitizerKind::KernelAddress | + SanitizerKind::HWAddress | + SanitizerKind::KernelHWAddress | SanitizerKind::MemTag | + SanitizerKind::Thread | SanitizerKind::Memory | + SanitizerKind::KernelMemory | SanitizerKind::SafeStack | + SanitizerKind::ShadowCallStack | SanitizerKind::Fuzzer | + SanitizerKind::FuzzerNoLink | + SanitizerKind::CFIUnrelatedCast | SanitizerKind::Null)) + llvm_unreachable("NYI"); + + // TODO: XRay + // TODO: PGO + + unsigned Count, Offset; + if (const auto *Attr = + D ? D->getAttr() : nullptr) { + llvm_unreachable("NYI"); + } else { + Count = CGM.getCodeGenOpts().PatchableFunctionEntryCount; + Offset = CGM.getCodeGenOpts().PatchableFunctionEntryOffset; + } + if (Count && Offset <= Count) { + llvm_unreachable("NYI"); + } + + // Add no-jump-tables value. + if (CGM.getCodeGenOpts().NoUseJumpTables) + llvm_unreachable("NYI"); + + // Add no-inline-line-tables value. + if (CGM.getCodeGenOpts().NoInlineLineTables) + llvm_unreachable("NYI"); + + // Add profile-sample-accurate value. + if (CGM.getCodeGenOpts().ProfileSampleAccurate) + llvm_unreachable("NYI"); + + if (!CGM.getCodeGenOpts().SampleProfileFile.empty()) + llvm_unreachable("NYI"); + + if (D && D->hasAttr()) + llvm_unreachable("NYI"); + + if (D && D->hasAttr()) + llvm_unreachable("NYI"); + + if (FD && getLangOpts().OpenCL) { + llvm_unreachable("NYI"); + } + + // If we are checking function types, emit a function type signature as + // prologue data. + if (FD && getLangOpts().CPlusPlus && SanOpts.has(SanitizerKind::Function)) { + llvm_unreachable("NYI"); + } + + // If we're checking nullability, we need to know whether we can check the + // return value. Initialize the falg to 'true' and refine it in + // buildParmDecl. + if (SanOpts.has(SanitizerKind::NullabilityReturn)) { + llvm_unreachable("NYI"); + } + + // If we're in C++ mode and the function name is "main", it is guaranteed to + // be norecurse by the standard (3.6.1.3 "The function main shall not be + // used within a program"). + // + // OpenCL C 2.0 v2.2-11 s6.9.i: + // Recursion is not supported. + // + // SYCL v1.2.1 s3.10: + // kernels cannot include RTTI information, exception cases, recursive + // code, virtual functions or make use of C++ libraries that are not + // compiled for the device. + if (FD && + ((getLangOpts().CPlusPlus && FD->isMain()) || getLangOpts().OpenCL || + getLangOpts().SYCLIsDevice | + (getLangOpts().CUDA && FD->hasAttr()))) + ; // TODO: support norecurse attr + + llvm::RoundingMode RM = getLangOpts().getDefaultRoundingMode(); + cir::fp::ExceptionBehavior FPExceptionBehavior = + ToConstrainedExceptMD(getLangOpts().getDefaultExceptionMode()); + builder.setDefaultConstrainedRounding(RM); + builder.setDefaultConstrainedExcept(FPExceptionBehavior); + if ((FD && (FD->UsesFPIntrin() || FD->hasAttr())) || + (!FD && (FPExceptionBehavior != cir::fp::ebIgnore || + RM != llvm::RoundingMode::NearestTiesToEven))) { + llvm_unreachable("NYI"); + } + + // TODO: stackrealign attr + + mlir::Block *EntryBB = &Fn.getBlocks().front(); + + // TODO: allocapt insertion? probably don't need for CIR + + // TODO: return value checking + + if (getDebugInfo()) { + llvm_unreachable("NYI"); + } + + if (ShouldInstrumentFunction()) { + llvm_unreachable("NYI"); + } + + // Since emitting the mcount call here impacts optimizations such as + // function inlining, we just add an attribute to insert a mcount call in + // backend. The attribute "counting-function" is set to mcount function name + // which is architecture dependent. + if (CGM.getCodeGenOpts().InstrumentForProfiling) { + llvm_unreachable("NYI"); + } + + if (CGM.getCodeGenOpts().PackedStack) { + llvm_unreachable("NYI"); + } + + if (CGM.getCodeGenOpts().WarnStackSize != UINT_MAX) { + llvm_unreachable("NYI"); + } + + // TODO: emitstartehspec + + // TODO: prologuecleanupdepth + + if (getLangOpts().OpenMP && CurCodeDecl) + llvm_unreachable("NYI"); + + // TODO: buildFunctionProlog + + { + // Set the insertion point in the builder to the beginning of the + // function body, it will be used throughout the codegen to create + // operations in this function. + builder.setInsertionPointToStart(EntryBB); + + // TODO: this should live in `buildFunctionProlog + // Declare all the function arguments in the symbol table. + for (const auto nameValue : llvm::zip(Args, EntryBB->getArguments())) { + auto *paramVar = std::get<0>(nameValue); + auto paramVal = std::get<1>(nameValue); + auto alignment = getContext().getDeclAlign(paramVar); + auto paramLoc = getLoc(paramVar->getSourceRange()); + paramVal.setLoc(paramLoc); + + mlir::Value addr; + if (failed(declare(paramVar, paramVar->getType(), paramLoc, alignment, + addr, true /*param*/))) + return; + + auto address = Address(addr, alignment); + setAddrOfLocalVar(paramVar, address); + + // Location of the store to the param storage tracked as beginning of + // the function body. + auto fnBodyBegin = getLoc(FD->getBody()->getBeginLoc()); + builder.create(fnBodyBegin, paramVal, addr); + } + assert(builder.getInsertionBlock() && "Should be valid"); + + auto FnEndLoc = getLoc(FD->getBody()->getEndLoc()); + + // When the current function is not void, create an address to store the + // result value. + if (FnRetCIRTy.has_value()) + buildAndUpdateRetAlloca(FnRetQualTy, FnEndLoc, + CGM.getNaturalTypeAlignment(FnRetQualTy)); + } + + if (D && isa(D) && cast(D)->isInstance()) { + CGM.getCXXABI().buildInstanceFunctionProlog(*this); + + const auto *MD = cast(D); + if (MD->getParent()->isLambda() && MD->getOverloadedOperator() == OO_Call) { + // We're in a lambda. + auto Fn = dyn_cast(CurFn); + assert(Fn && "other callables NYI"); + Fn.setLambdaAttr(mlir::UnitAttr::get(builder.getContext())); + + // Figure out the captures. + MD->getParent()->getCaptureFields(LambdaCaptureFields, + LambdaThisCaptureField); + if (LambdaThisCaptureField) { + llvm_unreachable("NYI"); + } + for (auto *FD : MD->getParent()->fields()) { + if (FD->hasCapturedVLAType()) { + llvm_unreachable("NYI"); + } + } + + } else { + // Not in a lambda; just use 'this' from the method. + // FIXME: Should we generate a new load for each use of 'this'? The fast + // register allocator would be happier... + CXXThisValue = CXXABIThisValue; + } + + // Check the 'this' pointer once per function, if it's available + if (CXXABIThisValue) { + SanitizerSet SkippedChecks; + SkippedChecks.set(SanitizerKind::ObjectSize, true); + QualType ThisTy = MD->getThisType(); + (void)ThisTy; + + // If this is the call operator of a lambda with no capture-default, it + // may have a staic invoker function, which may call this operator with + // a null 'this' pointer. + if (isLambdaCallOperator(MD) && + MD->getParent()->getLambdaCaptureDefault() == LCD_None) + SkippedChecks.set(SanitizerKind::Null, true); + + assert(!UnimplementedFeature::buildTypeCheck() && "NYI"); + } + } + + // If any of the arguments have a variably modified type, make sure to emit + // the type size. + for (FunctionArgList::const_iterator i = Args.begin(), e = Args.end(); i != e; + ++i) { + const VarDecl *VD = *i; + + // Dig out the type as written from ParmVarDecls; it's unclear whether the + // standard (C99 6.9.1p10) requires this, but we're following the + // precedent set by gcc. + QualType Ty; + if (const auto *PVD = dyn_cast(VD)) + Ty = PVD->getOriginalType(); + else + Ty = VD->getType(); + + if (Ty->isVariablyModifiedType()) + llvm_unreachable("NYI"); + } + // Emit a location at the end of the prologue. + if (getDebugInfo()) + llvm_unreachable("NYI"); + + // TODO: Do we need to handle this in two places like we do with + // target-features/target-cpu? + if (CurFuncDecl) + if (const auto *VecWidth = CurFuncDecl->getAttr()) + llvm_unreachable("NYI"); +} + +/// ShouldInstrumentFunction - Return true if the current function should be +/// instrumented with __cyg_profile_func_* calls +bool CIRGenFunction::ShouldInstrumentFunction() { + if (!CGM.getCodeGenOpts().InstrumentFunctions && + !CGM.getCodeGenOpts().InstrumentFunctionsAfterInlining && + !CGM.getCodeGenOpts().InstrumentFunctionEntryBare) + return false; + + llvm_unreachable("NYI"); +} + +mlir::LogicalResult CIRGenFunction::buildFunctionBody(const clang::Stmt *Body) { + // TODO: incrementProfileCounter(Body); + + // We start with function level scope for variables. + SymTableScopeTy varScope(symbolTable); + + auto result = mlir::LogicalResult::success(); + if (const CompoundStmt *S = dyn_cast(Body)) + result = buildCompoundStmtWithoutScope(*S); + else + result = buildStmt(Body, /*useCurrentScope*/ true); + + // This is checked after emitting the function body so we know if there are + // any permitted infinite loops. + // TODO: if (checkIfFunctionMustProgress()) + // CurFn->addFnAttr(llvm::Attribute::MustProgress); + return result; +} + +clang::QualType CIRGenFunction::buildFunctionArgList(clang::GlobalDecl GD, + FunctionArgList &Args) { + const auto *FD = cast(GD.getDecl()); + QualType ResTy = FD->getReturnType(); + + const auto *MD = dyn_cast(FD); + if (MD && MD->isInstance()) { + if (CGM.getCXXABI().HasThisReturn(GD)) + llvm_unreachable("NYI"); + else if (CGM.getCXXABI().hasMostDerivedReturn(GD)) + llvm_unreachable("NYI"); + CGM.getCXXABI().buildThisParam(*this, Args); + } + + // The base version of an inheriting constructor whose constructed base is a + // virtual base is not passed any arguments (because it doesn't actually + // call the inherited constructor). + bool PassedParams = true; + if (const auto *CD = dyn_cast(FD)) + if (auto Inherited = CD->getInheritedConstructor()) + PassedParams = + getTypes().inheritingCtorHasParams(Inherited, GD.getCtorType()); + + if (PassedParams) { + for (auto *Param : FD->parameters()) { + Args.push_back(Param); + if (!Param->hasAttr()) + continue; + + auto *Implicit = ImplicitParamDecl::Create( + getContext(), Param->getDeclContext(), Param->getLocation(), + /*Id=*/nullptr, getContext().getSizeType(), ImplicitParamDecl::Other); + SizeArguments[Param] = Implicit; + Args.push_back(Implicit); + } + } + + if (MD && (isa(MD) || isa(MD))) + CGM.getCXXABI().addImplicitStructorParams(*this, ResTy, Args); + + return ResTy; +} + +static std::string getVersionedTmpName(llvm::StringRef name, unsigned cnt) { + SmallString<256> Buffer; + llvm::raw_svector_ostream Out(Buffer); + Out << name << cnt; + return std::string(Out.str()); +} + +std::string CIRGenFunction::getCounterAggTmpAsString() { + return getVersionedTmpName("agg.tmp", CounterAggTmp++); +} + +std::string CIRGenFunction::getCounterRefTmpAsString() { + return getVersionedTmpName("ref.tmp", CounterRefTmp++); +} + +void CIRGenFunction::buildNullInitialization(mlir::Location loc, + Address DestPtr, QualType Ty) { + // Ignore empty classes in C++. + if (getLangOpts().CPlusPlus) { + if (const RecordType *RT = Ty->getAs()) { + if (cast(RT->getDecl())->isEmpty()) + return; + } + } + + // Cast the dest ptr to the appropriate i8 pointer type. + if (builder.isInt8Ty(DestPtr.getElementType())) { + llvm_unreachable("NYI"); + } + + // Get size and alignment info for this aggregate. + CharUnits size = getContext().getTypeSizeInChars(Ty); + [[maybe_unused]] mlir::Attribute SizeVal{}; + [[maybe_unused]] const VariableArrayType *vla = nullptr; + + // Don't bother emitting a zero-byte memset. + if (size.isZero()) { + // But note that getTypeInfo returns 0 for a VLA. + if (const VariableArrayType *vlaType = dyn_cast_or_null( + getContext().getAsArrayType(Ty))) { + llvm_unreachable("NYI"); + } else { + return; + } + } else { + SizeVal = CGM.getSize(size); + } + + // If the type contains a pointer to data member we can't memset it to zero. + // Instead, create a null constant and copy it to the destination. + // TODO: there are other patterns besides zero that we can usefully memset, + // like -1, which happens to be the pattern used by member-pointers. + if (!CGM.getTypes().isZeroInitializable(Ty)) { + llvm_unreachable("NYI"); + } + + // In LLVM Codegen: otherwise, just memset the whole thing to zero using + // Builder.CreateMemSet. In CIR just emit a store of #cir.zero to the + // respective address. + // Builder.CreateMemSet(DestPtr, Builder.getInt8(0), SizeVal, false); + builder.createStore(loc, builder.getZero(loc, getTypes().ConvertType(Ty)), + DestPtr); +} + +CIRGenFunction::CIRGenFPOptionsRAII::CIRGenFPOptionsRAII(CIRGenFunction &CGF, + const clang::Expr *E) + : CGF(CGF) { + ConstructorHelper(E->getFPFeaturesInEffect(CGF.getLangOpts())); +} + +CIRGenFunction::CIRGenFPOptionsRAII::CIRGenFPOptionsRAII(CIRGenFunction &CGF, + FPOptions FPFeatures) + : CGF(CGF) { + ConstructorHelper(FPFeatures); +} + +void CIRGenFunction::CIRGenFPOptionsRAII::ConstructorHelper( + FPOptions FPFeatures) { + OldFPFeatures = CGF.CurFPFeatures; + CGF.CurFPFeatures = FPFeatures; + + OldExcept = CGF.builder.getDefaultConstrainedExcept(); + OldRounding = CGF.builder.getDefaultConstrainedRounding(); + + if (OldFPFeatures == FPFeatures) + return; + + // TODO(cir): create guard to restore fast math configurations. + assert(!UnimplementedFeature::fastMathGuard()); + + llvm::RoundingMode NewRoundingBehavior = FPFeatures.getRoundingMode(); + // TODO(cir): override rounding behaviour once FM configs are guarded. + auto NewExceptionBehavior = + ToConstrainedExceptMD(static_cast( + FPFeatures.getExceptionMode())); + // TODO(cir): override exception behaviour once FM configs are guarded. + + // TODO(cir): override FP flags once FM configs are guarded. + assert(!UnimplementedFeature::fastMathFlags()); + + assert((CGF.CurFuncDecl == nullptr || CGF.builder.getIsFPConstrained() || + isa(CGF.CurFuncDecl) || + isa(CGF.CurFuncDecl) || + (NewExceptionBehavior == fp::ebIgnore && + NewRoundingBehavior == llvm::RoundingMode::NearestTiesToEven)) && + "FPConstrained should be enabled on entire function"); + + // TODO(cir): mark CIR function with fast math attributes. + assert(!UnimplementedFeature::fastMathFuncAttributes()); +} + +CIRGenFunction::CIRGenFPOptionsRAII::~CIRGenFPOptionsRAII() { + CGF.CurFPFeatures = OldFPFeatures; + CGF.builder.setDefaultConstrainedExcept(OldExcept); + CGF.builder.setDefaultConstrainedRounding(OldRounding); +} + +// TODO(cir): should be shared with LLVM codegen. +bool CIRGenFunction::shouldNullCheckClassCastValue(const CastExpr *CE) { + const Expr *E = CE->getSubExpr(); + + if (CE->getCastKind() == CK_UncheckedDerivedToBase) + return false; + + if (isa(E->IgnoreParens())) { + // We always assume that 'this' is never null. + return false; + } + + if (const ImplicitCastExpr *ICE = dyn_cast(CE)) { + // And that glvalue casts are never null. + if (ICE->isGLValue()) + return false; + } + + return true; +} + +void CIRGenFunction::buildDeclRefExprDbgValue(const DeclRefExpr *E, + const APValue &Init) { + assert(!UnimplementedFeature::generateDebugInfo()); +} + +Address CIRGenFunction::buildVAListRef(const Expr *E) { + if (getContext().getBuiltinVaListType()->isArrayType()) + return buildPointerWithAlignment(E); + return buildLValue(E).getAddress(); +} + +// Emits an error if we don't have a valid set of target features for the +// called function. +void CIRGenFunction::checkTargetFeatures(const CallExpr *E, + const FunctionDecl *TargetDecl) { + return checkTargetFeatures(E->getBeginLoc(), TargetDecl); +} + +// Emits an error if we don't have a valid set of target features for the +// called function. +void CIRGenFunction::checkTargetFeatures(SourceLocation Loc, + const FunctionDecl *TargetDecl) { + // Early exit if this is an indirect call. + if (!TargetDecl) + return; + + // Get the current enclosing function if it exists. If it doesn't + // we can't check the target features anyhow. + const FunctionDecl *FD = dyn_cast_or_null(CurCodeDecl); + if (!FD) + return; + + // Grab the required features for the call. For a builtin this is listed in + // the td file with the default cpu, for an always_inline function this is any + // listed cpu and any listed features. + unsigned BuiltinID = TargetDecl->getBuiltinID(); + std::string MissingFeature; + llvm::StringMap CallerFeatureMap; + CGM.getASTContext().getFunctionFeatureMap(CallerFeatureMap, FD); + if (BuiltinID) { + StringRef FeatureList( + getContext().BuiltinInfo.getRequiredFeatures(BuiltinID)); + if (!Builtin::evaluateRequiredTargetFeatures(FeatureList, + CallerFeatureMap)) { + CGM.getDiags().Report(Loc, diag::err_builtin_needs_feature) + << TargetDecl->getDeclName() << FeatureList; + } + } else if (!TargetDecl->isMultiVersion() && + TargetDecl->hasAttr()) { + // Get the required features for the callee. + + const TargetAttr *TD = TargetDecl->getAttr(); + ParsedTargetAttr ParsedAttr = getContext().filterFunctionTargetAttrs(TD); + + SmallVector ReqFeatures; + llvm::StringMap CalleeFeatureMap; + getContext().getFunctionFeatureMap(CalleeFeatureMap, TargetDecl); + + for (const auto &F : ParsedAttr.Features) { + if (F[0] == '+' && CalleeFeatureMap.lookup(F.substr(1))) + ReqFeatures.push_back(StringRef(F).substr(1)); + } + + for (const auto &F : CalleeFeatureMap) { + // Only positive features are "required". + if (F.getValue()) + ReqFeatures.push_back(F.getKey()); + } + if (!llvm::all_of(ReqFeatures, [&](StringRef Feature) { + if (!CallerFeatureMap.lookup(Feature)) { + MissingFeature = Feature.str(); + return false; + } + return true; + })) + CGM.getDiags().Report(Loc, diag::err_function_needs_feature) + << FD->getDeclName() << TargetDecl->getDeclName() << MissingFeature; + } else if (!FD->isMultiVersion() && FD->hasAttr()) { + llvm::StringMap CalleeFeatureMap; + getContext().getFunctionFeatureMap(CalleeFeatureMap, TargetDecl); + + for (const auto &F : CalleeFeatureMap) { + if (F.getValue() && (!CallerFeatureMap.lookup(F.getKey()) || + !CallerFeatureMap.find(F.getKey())->getValue())) + CGM.getDiags().Report(Loc, diag::err_function_needs_feature) + << FD->getDeclName() << TargetDecl->getDeclName() << F.getKey(); + } + } +} diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h new file mode 100644 index 000000000000..3428c7f254df --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -0,0 +1,1946 @@ +//===-- CIRGenFunction.h - Per-Function state for CIR gen -------*- C++ -*-===// +// +// 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 is the internal per-function state used for CIR translation. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CIRGENFUNCTION_H +#define LLVM_CLANG_LIB_CIR_CIRGENFUNCTION_H + +#include "CIRGenBuilder.h" +#include "CIRGenCall.h" +#include "CIRGenModule.h" +#include "CIRGenTypeCache.h" +#include "CIRGenValue.h" +#include "EHScopeStack.h" + +#include "clang/AST/BaseSubobject.h" +#include "clang/AST/CurrentSourceLocExprScope.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" +#include "clang/Basic/ABI.h" +#include "clang/Basic/TargetInfo.h" + +#include "mlir/IR/TypeRange.h" +#include "mlir/IR/Value.h" + +namespace clang { +class Expr; +} // namespace clang + +namespace mlir { +namespace func { +class CallOp; +} +} // namespace mlir + +namespace { +class ScalarExprEmitter; +class AggExprEmitter; +} // namespace + +namespace cir { + +// FIXME: for now we are reusing this from lib/Clang/CIRGenFunction.h, which +// isn't available in the include dir. Same for getEvaluationKind below. +enum TypeEvaluationKind { TEK_Scalar, TEK_Complex, TEK_Aggregate }; +struct CGCoroData; + +class CIRGenFunction : public CIRGenTypeCache { +public: + CIRGenModule &CGM; + +private: + friend class ::ScalarExprEmitter; + friend class ::AggExprEmitter; + + /// 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; + + /// ------- + /// Goto + /// ------- + + /// A jump destination is an abstract label, branching to which may + /// require a jump out through normal cleanups. + struct JumpDest { + JumpDest() = default; + JumpDest(mlir::Block *Block) : Block(Block) {} + + bool isValid() const { return Block != nullptr; } + mlir::Block *getBlock() const { return Block; } + mlir::Block *Block = nullptr; + }; + + /// Track mlir Blocks for each C/C++ label. + llvm::DenseMap LabelMap; + JumpDest &getJumpDestForLabel(const clang::LabelDecl *D); + + /// ------- + /// Lexical Scope: to be read as in the meaning in CIR, a scope is always + /// related with initialization and destruction of objects. + /// ------- + +public: + // Represents a cir.scope, cir.if, and then/else regions. I.e. lexical + // scopes that require cleanups. + struct LexicalScopeContext { + private: + // Block containing cleanup code for things initialized in this + // lexical context (scope). + mlir::Block *CleanupBlock = nullptr; + + // Points to scope entry block. This is useful, for instance, for + // helping to insert allocas before finalizing any recursive codegen + // from switches. + mlir::Block *EntryBlock; + + // On a coroutine body, the OnFallthrough sub stmt holds the handler + // (CoreturnStmt) for control flow falling off the body. Keep track + // of emitted co_return in this scope and allow OnFallthrough to be + // skipeed. + bool HasCoreturn = false; + + // FIXME: perhaps we can use some info encoded in operations. + enum Kind { + Regular, // cir.if, cir.scope, if_regions + Ternary, // cir.ternary + Switch // cir.switch + } ScopeKind = Regular; + + public: + unsigned Depth = 0; + bool HasReturn = false; + + LexicalScopeContext(mlir::Location loc, mlir::Block *eb) + : EntryBlock(eb), BeginLoc(loc), EndLoc(loc) { + // Has multiple locations: overwrite with separate start and end locs. + if (const auto fusedLoc = loc.dyn_cast()) { + assert(fusedLoc.getLocations().size() == 2 && "too many locations"); + BeginLoc = fusedLoc.getLocations()[0]; + EndLoc = fusedLoc.getLocations()[1]; + } + + assert(EntryBlock && "expected valid block"); + } + + ~LexicalScopeContext() = default; + + // --- + // Coroutine tracking + // --- + bool hasCoreturn() const { return HasCoreturn; } + void setCoreturn() { HasCoreturn = true; } + + // --- + // Kind + // --- + bool isRegular() { return ScopeKind == Kind::Regular; } + bool isSwitch() { return ScopeKind == Kind::Switch; } + bool isTernary() { return ScopeKind == Kind::Ternary; } + + void setAsSwitch() { ScopeKind = Kind::Switch; } + void setAsTernary() { ScopeKind = Kind::Ternary; } + + // --- + // Goto handling + // --- + + // Lazy create cleanup block or return what's available. + mlir::Block *getOrCreateCleanupBlock(mlir::OpBuilder &builder) { + if (CleanupBlock) + return getCleanupBlock(builder); + return createCleanupBlock(builder); + } + + mlir::Block *getCleanupBlock(mlir::OpBuilder &builder) { + return CleanupBlock; + } + mlir::Block *createCleanupBlock(mlir::OpBuilder &builder) { + { + // Create the cleanup block but dont hook it up around just yet. + mlir::OpBuilder::InsertionGuard guard(builder); + CleanupBlock = builder.createBlock(builder.getBlock()->getParent()); + } + assert(builder.getInsertionBlock() && "Should be valid"); + return CleanupBlock; + } + + // Goto's introduced in this scope but didn't get fixed. + llvm::SmallVector, 4> + PendingGotos; + + // Labels solved inside this scope. + llvm::SmallPtrSet SolvedLabels; + + // --- + // Return handling + // --- + + private: + // On switches we need one return block per region, since cases don't + // have their own scopes but are distinct regions nonetheless. + llvm::SmallVector RetBlocks; + llvm::SmallVector> RetLocs; + unsigned int CurrentSwitchRegionIdx = -1; + + // There's usually only one ret block per scope, but this needs to be + // get or create because of potential unreachable return statements, note + // that for those, all source location maps to the first one found. + mlir::Block *createRetBlock(CIRGenFunction &CGF, mlir::Location loc) { + assert((isSwitch() || RetBlocks.size() == 0) && + "only switches can hold more than one ret block"); + + // Create the cleanup block but dont hook it up around just yet. + mlir::OpBuilder::InsertionGuard guard(CGF.builder); + auto *b = CGF.builder.createBlock(CGF.builder.getBlock()->getParent()); + RetBlocks.push_back(b); + RetLocs.push_back(loc); + return b; + } + + public: + void updateCurrentSwitchCaseRegion() { CurrentSwitchRegionIdx++; } + llvm::ArrayRef getRetBlocks() { return RetBlocks; } + llvm::ArrayRef> getRetLocs() { + return RetLocs; + } + + mlir::Block *getOrCreateRetBlock(CIRGenFunction &CGF, mlir::Location loc) { + unsigned int regionIdx = 0; + if (isSwitch()) + regionIdx = CurrentSwitchRegionIdx; + if (regionIdx >= RetBlocks.size()) + return createRetBlock(CGF, loc); + return &*RetBlocks.back(); + } + + // Scope entry block tracking + mlir::Block *getEntryBlock() { return EntryBlock; } + + mlir::Location BeginLoc, EndLoc; + }; + +private: + class LexicalScopeGuard { + CIRGenFunction &CGF; + LexicalScopeContext *OldVal = nullptr; + + public: + LexicalScopeGuard(CIRGenFunction &c, LexicalScopeContext *L) : CGF(c) { + if (CGF.currLexScope) { + OldVal = CGF.currLexScope; + L->Depth++; + } + CGF.currLexScope = L; + } + + LexicalScopeGuard(const LexicalScopeGuard &) = delete; + LexicalScopeGuard &operator=(const LexicalScopeGuard &) = delete; + LexicalScopeGuard &operator=(LexicalScopeGuard &&other) = delete; + + void cleanup(); + void restore() { CGF.currLexScope = OldVal; } + ~LexicalScopeGuard() { + cleanup(); + restore(); + } + }; + + LexicalScopeContext *currLexScope = nullptr; + + // --------------------- + // Opaque value handling + // --------------------- + + /// Keeps track of the current set of opaque value expressions. + llvm::DenseMap OpaqueLValues; + llvm::DenseMap OpaqueRValues; + +public: + /// A non-RAII class containing all the information about a bound + /// opaque value. OpaqueValueMapping, below, is a RAII wrapper for + /// this which makes individual mappings very simple; using this + /// class directly is useful when you have a variable number of + /// opaque values or don't want the RAII functionality for some + /// reason. + class OpaqueValueMappingData { + const OpaqueValueExpr *OpaqueValue; + bool BoundLValue; + + OpaqueValueMappingData(const OpaqueValueExpr *ov, bool boundLValue) + : OpaqueValue(ov), BoundLValue(boundLValue) {} + + public: + OpaqueValueMappingData() : OpaqueValue(nullptr) {} + + static bool shouldBindAsLValue(const Expr *expr) { + // gl-values should be bound as l-values for obvious reasons. + // Records should be bound as l-values because IR generation + // always keeps them in memory. Expressions of function type + // act exactly like l-values but are formally required to be + // r-values in C. + return expr->isGLValue() || expr->getType()->isFunctionType() || + hasAggregateEvaluationKind(expr->getType()); + } + + static OpaqueValueMappingData + bind(CIRGenFunction &CGF, const OpaqueValueExpr *ov, const Expr *e) { + if (shouldBindAsLValue(ov)) + return bind(CGF, ov, CGF.buildLValue(e)); + return bind(CGF, ov, CGF.buildAnyExpr(e)); + } + + static OpaqueValueMappingData + bind(CIRGenFunction &CGF, const OpaqueValueExpr *ov, const LValue &lv) { + assert(shouldBindAsLValue(ov)); + CGF.OpaqueLValues.insert(std::make_pair(ov, lv)); + return OpaqueValueMappingData(ov, true); + } + + static OpaqueValueMappingData + bind(CIRGenFunction &CGF, const OpaqueValueExpr *ov, const RValue &rv) { + assert(!shouldBindAsLValue(ov)); + CGF.OpaqueRValues.insert(std::make_pair(ov, rv)); + + OpaqueValueMappingData data(ov, false); + + // Work around an extremely aggressive peephole optimization in + // EmitScalarConversion which assumes that all other uses of a + // value are extant. + assert(!UnimplementedFeature::peepholeProtection() && "NYI"); + return data; + } + + bool isValid() const { return OpaqueValue != nullptr; } + void clear() { OpaqueValue = nullptr; } + + void unbind(CIRGenFunction &CGF) { + assert(OpaqueValue && "no data to unbind!"); + + if (BoundLValue) { + CGF.OpaqueLValues.erase(OpaqueValue); + } else { + CGF.OpaqueRValues.erase(OpaqueValue); + assert(!UnimplementedFeature::peepholeProtection() && "NYI"); + } + } + }; + + /// An RAII object to set (and then clear) a mapping for an OpaqueValueExpr. + class OpaqueValueMapping { + CIRGenFunction &CGF; + OpaqueValueMappingData Data; + + public: + static bool shouldBindAsLValue(const Expr *expr) { + return OpaqueValueMappingData::shouldBindAsLValue(expr); + } + + /// Build the opaque value mapping for the given conditional + /// operator if it's the GNU ?: extension. This is a common + /// enough pattern that the convenience operator is really + /// helpful. + /// + OpaqueValueMapping(CIRGenFunction &CGF, + const AbstractConditionalOperator *op) + : CGF(CGF) { + if (isa(op)) + // Leave Data empty. + return; + + const BinaryConditionalOperator *e = cast(op); + Data = OpaqueValueMappingData::bind(CGF, e->getOpaqueValue(), + e->getCommon()); + } + + /// Build the opaque value mapping for an OpaqueValueExpr whose source + /// expression is set to the expression the OVE represents. + OpaqueValueMapping(CIRGenFunction &CGF, const OpaqueValueExpr *OV) + : CGF(CGF) { + if (OV) { + assert(OV->getSourceExpr() && "wrong form of OpaqueValueMapping used " + "for OVE with no source expression"); + Data = OpaqueValueMappingData::bind(CGF, OV, OV->getSourceExpr()); + } + } + + OpaqueValueMapping(CIRGenFunction &CGF, const OpaqueValueExpr *opaqueValue, + LValue lvalue) + : CGF(CGF), + Data(OpaqueValueMappingData::bind(CGF, opaqueValue, lvalue)) {} + + OpaqueValueMapping(CIRGenFunction &CGF, const OpaqueValueExpr *opaqueValue, + RValue rvalue) + : CGF(CGF), + Data(OpaqueValueMappingData::bind(CGF, opaqueValue, rvalue)) {} + + void pop() { + Data.unbind(CGF); + Data.clear(); + } + + ~OpaqueValueMapping() { + if (Data.isValid()) + Data.unbind(CGF); + } + }; + +private: + /// Declare a variable in the current scope, return success if the variable + /// wasn't declared yet. + mlir::LogicalResult declare(const clang::Decl *var, clang::QualType ty, + mlir::Location loc, clang::CharUnits alignment, + mlir::Value &addr, bool isParam = false); + + /// Declare a variable in the current scope but take an Address as input. + mlir::LogicalResult declare(Address addr, const clang::Decl *var, + clang::QualType ty, mlir::Location loc, + clang::CharUnits alignment, mlir::Value &addrVal, + bool isParam = false); + +public: + // FIXME(cir): move this to CIRGenBuider.h + mlir::Value buildAlloca(llvm::StringRef name, clang::QualType ty, + mlir::Location loc, clang::CharUnits alignment, + bool insertIntoFnEntryBlock = false); + mlir::Value buildAlloca(llvm::StringRef name, mlir::Type ty, + mlir::Location loc, clang::CharUnits alignment, + bool insertIntoFnEntryBlock = false); + mlir::Value buildAlloca(llvm::StringRef name, mlir::Type ty, + mlir::Location loc, clang::CharUnits alignment, + mlir::OpBuilder::InsertPoint ip); + +private: + void buildAndUpdateRetAlloca(clang::QualType ty, mlir::Location loc, + clang::CharUnits alignment); + + // 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(); } + }; + + /// ------- + /// Source Location tracking + /// ------- + +public: + /// Use to track source locations across nested visitor traversals. + /// Always use a `SourceLocRAIIObject` to change currSrcLoc. + std::optional currSrcLoc; + class SourceLocRAIIObject { + CIRGenFunction &P; + std::optional OldVal; + + public: + SourceLocRAIIObject(CIRGenFunction &p, mlir::Location Value) : P(p) { + if (P.currSrcLoc) + OldVal = P.currSrcLoc; + P.currSrcLoc = Value; + } + + /// Can be used to restore the state early, before the dtor + /// is run. + void restore() { P.currSrcLoc = OldVal; } + ~SourceLocRAIIObject() { restore(); } + }; + + using SymTableScopeTy = + llvm::ScopedHashTableScope; + + enum class EvaluationOrder { + ///! No langauge constraints on evaluation order. + Default, + ///! Language semantics require left-to-right evaluation + ForceLeftToRight, + ///! Language semantics require right-to-left evaluation. + ForceRightToLeft + }; + + /// Situations in which we might emit a check for the suitability of a pointer + /// or glvalue. Needs to be kept in sync with ubsan_handlers.cpp in + /// compiler-rt. + enum TypeCheckKind { + /// Checking the operand of a load. Must be suitably sized and aligned. + TCK_Load, + /// Checking the destination of a store. Must be suitably sized and aligned. + TCK_Store, + /// Checking the bound value in a reference binding. Must be suitably sized + /// and aligned, but is not required to refer to an object (until the + /// reference is used), per core issue 453. + TCK_ReferenceBinding, + /// Checking the object expression in a non-static data member access. Must + /// be an object within its lifetime. + TCK_MemberAccess, + /// Checking the 'this' pointer for a call to a non-static member function. + /// Must be an object within its lifetime. + TCK_MemberCall, + /// Checking the 'this' pointer for a constructor call. + TCK_ConstructorCall, + }; + + // Holds coroutine data if the current function is a coroutine. We use a + // wrapper to manage its lifetime, so that we don't have to define CGCoroData + // in this header. + struct CGCoroInfo { + std::unique_ptr Data; + CGCoroInfo(); + ~CGCoroInfo(); + }; + CGCoroInfo CurCoro; + + bool isCoroutine() const { return CurCoro.Data != nullptr; } + + /// The GlobalDecl for the current function being compiled. + clang::GlobalDecl CurGD; + + /// Unified return block. + /// Not that for LLVM codegen this is a memeber variable instead. + JumpDest ReturnBlock() { + return JumpDest(currLexScope->getOrCreateCleanupBlock(builder)); + } + + /// The temporary alloca to hold the return value. This is + /// invalid iff the function has no return value. + Address ReturnValue = Address::invalid(); + + /// Tracks function scope overall cleanup handling. + EHScopeStack EHStack; + llvm::SmallVector LifetimeExtendedCleanupStack; + + /// A mapping from NRVO variables to the flags used to indicate + /// when the NRVO has been applied to this variable. + llvm::DenseMap NRVOFlags; + + /// Counts of the number return expressions in the function. + unsigned NumReturnExprs = 0; + + clang::QualType FnRetQualTy; + std::optional FnRetCIRTy; + std::optional FnRetAlloca; + + llvm::DenseMap + LambdaCaptureFields; + clang::FieldDecl *LambdaThisCaptureField = nullptr; + + void buildForwardingCallToLambda(const CXXMethodDecl *LambdaCallOperator, + CallArgList &CallArgs); + void buildLambdaDelegatingInvokeBody(const CXXMethodDecl *MD); + void buildLambdaStaticInvokeBody(const CXXMethodDecl *MD); + + LValue buildPredefinedLValue(const PredefinedExpr *E); + + /// When generating code for a C++ member function, this will + /// hold the implicit 'this' declaration. + clang::ImplicitParamDecl *CXXABIThisDecl = nullptr; + mlir::Value CXXABIThisValue = nullptr; + mlir::Value CXXThisValue = nullptr; + clang::CharUnits CXXABIThisAlignment; + clang::CharUnits CXXThisAlignment; + + /// When generating code for a constructor or destructor, this will hold the + /// implicit argument (e.g. VTT). + ImplicitParamDecl *CXXStructorImplicitParamDecl{}; + mlir::Value CXXStructorImplicitParamValue{}; + + /// The value of 'this' to sue when evaluating CXXDefaultInitExprs within this + /// expression. + Address CXXDefaultInitExprThis = Address::invalid(); + + // Holds the Decl for the current outermost non-closure context + const clang::Decl *CurFuncDecl = nullptr; + /// This is the inner-most code context, which includes blocks. + const clang::Decl *CurCodeDecl; + const CIRGenFunctionInfo *CurFnInfo; + clang::QualType FnRetTy; + + /// This is the current function or global initializer that is generated code + /// for. + mlir::Operation *CurFn = nullptr; + + /// Save Parameter Decl for coroutine. + llvm::SmallVector FnArgs; + + // The CallExpr within the current statement that the musttail attribute + // applies to. nullptr if there is no 'musttail' on the current statement. + const clang::CallExpr *MustTailCall = nullptr; + + clang::ASTContext &getContext() const; + + CIRGenBuilderTy &getBuilder() { return builder; } + + CIRGenModule &getCIRGenModule() { return CGM; } + + mlir::Block *getCurFunctionEntryBlock() { + auto Fn = dyn_cast(CurFn); + assert(Fn && "other callables NYI"); + return &Fn.getRegion().front(); + } + + /// Sanitizers enabled for this function. + clang::SanitizerSet SanOpts; + + class CIRGenFPOptionsRAII { + public: + CIRGenFPOptionsRAII(CIRGenFunction &CGF, FPOptions FPFeatures); + CIRGenFPOptionsRAII(CIRGenFunction &CGF, const clang::Expr *E); + ~CIRGenFPOptionsRAII(); + + private: + void ConstructorHelper(clang::FPOptions FPFeatures); + CIRGenFunction &CGF; + clang::FPOptions OldFPFeatures; + fp::ExceptionBehavior OldExcept; + llvm::RoundingMode OldRounding; + }; + clang::FPOptions CurFPFeatures; + + /// The symbol table maps a variable name to a value in the current scope. + /// Entering a function creates a new scope, and the function arguments are + /// added to the mapping. When the processing of a function is terminated, + /// the scope is destroyed and the mappings created in this scope are + /// dropped. + using SymTableTy = llvm::ScopedHashTable; + SymTableTy symbolTable; + /// True if we need to emit the life-time markers. This is initially set in + /// the constructor, but could be overwrriten to true if this is a coroutine. + bool ShouldEmitLifetimeMarkers; + + using DeclMapTy = llvm::DenseMap; + /// This keeps track of the CIR allocas or globals for local C + /// delcs. + DeclMapTy LocalDeclMap; + + /// Whether llvm.stacksave has been called. Used to avoid + /// calling llvm.stacksave for multiple VLAs in the same scope. + /// TODO: Translate to MLIR + bool DidCallStackSave = false; + + /// Whether we processed a Microsoft-style asm block during CIRGen. These can + /// potentially set the return value. + bool SawAsmBlock = false; + + /// True if CodeGen currently emits code inside preserved access index region. + bool IsInPreservedAIRegion = false; + + /// In C++, whether we are code generating a thunk. This controls whether we + /// should emit cleanups. + bool CurFuncIsThunk = false; + + /// Hold counters for incrementally naming temporaries + unsigned CounterRefTmp = 0; + unsigned CounterAggTmp = 0; + std::string getCounterRefTmpAsString(); + std::string getCounterAggTmpAsString(); + + mlir::Type convertTypeForMem(QualType T); + + mlir::Type ConvertType(clang::QualType T); + mlir::Type ConvertType(const TypeDecl *T) { + return ConvertType(getContext().getTypeDeclType(T)); + } + + /// Return the TypeEvaluationKind of QualType \c T. + static TypeEvaluationKind getEvaluationKind(clang::QualType T); + + static bool hasScalarEvaluationKind(clang::QualType T) { + return getEvaluationKind(T) == TEK_Scalar; + } + + static bool hasAggregateEvaluationKind(clang::QualType T) { + return getEvaluationKind(T) == TEK_Aggregate; + } + + CIRGenFunction(CIRGenModule &CGM, CIRGenBuilderTy &builder, + bool suppressNewContext = false); + + CIRGenTypes &getTypes() const { return CGM.getTypes(); } + + const TargetInfo &getTarget() const { return CGM.getTarget(); } + + /// Helpers to convert Clang's SourceLocation to a MLIR Location. + mlir::Location getLoc(clang::SourceLocation SLoc); + + mlir::Location getLoc(clang::SourceRange SLoc); + + mlir::Location getLoc(mlir::Location lhs, mlir::Location rhs); + + const clang::LangOptions &getLangOpts() const { return CGM.getLangOpts(); } + + // TODO: This is currently just a dumb stub. But we want to be able to clearly + // assert where we arne't doing things that we know we should and will crash + // as soon as we add a DebugInfo type to this class. + std::nullptr_t *getDebugInfo() { return nullptr; } + + void buildReturnOfRValue(mlir::Location loc, RValue RV, QualType Ty); + + /// 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}); + // Add to the symbol table if not there already. + if (symbolTable.count(VD)) + return; + symbolTable.insert(VD, Addr.getPointer()); + } + + /// True if an insertion point is defined. If not, this indicates that the + /// current code being emitted is unreachable. + /// FIXME(cir): we need to inspect this and perhaps use a cleaner mechanism + /// since we don't yet force null insertion point to designate behavior (like + /// LLVM's codegen does) and we probably shouldn't. + bool HaveInsertPoint() const { + return builder.getInsertionBlock() != nullptr; + } + + /// Whether any type-checking sanitizers are enabled. If \c false, calls to + /// buildTypeCheck can be skipped. + bool sanitizePerformTypeCheck() const; + + void buildTypeCheck(TypeCheckKind TCK, clang::SourceLocation Loc, + mlir::Value V, clang::QualType Type, + clang::CharUnits Alignment = clang::CharUnits::Zero(), + clang::SanitizerSet SkippedChecks = clang::SanitizerSet(), + std::optional ArraySize = std::nullopt); + + void buildAggExpr(const clang::Expr *E, AggValueSlot Slot); + + /// Emits a reference binding to the passed in expression. + RValue buildReferenceBindingToExpr(const Expr *E); + + LValue buildCastLValue(const CastExpr *E); + + void buildCXXConstructExpr(const clang::CXXConstructExpr *E, + AggValueSlot Dest); + + void buildCXXConstructorCall(const clang::CXXConstructorDecl *D, + clang::CXXCtorType Type, bool ForVirtualBase, + bool Delegating, AggValueSlot ThisAVS, + const clang::CXXConstructExpr *E); + + void buildCXXConstructorCall(const clang::CXXConstructorDecl *D, + clang::CXXCtorType Type, bool ForVirtualBase, + bool Delegating, Address This, CallArgList &Args, + AggValueSlot::Overlap_t Overlap, + clang::SourceLocation Loc, + bool NewPointerIsChecked); + + RValue buildCXXMemberOrOperatorCall( + const clang::CXXMethodDecl *Method, const CIRGenCallee &Callee, + ReturnValueSlot ReturnValue, mlir::Value This, mlir::Value ImplicitParam, + clang::QualType ImplicitParamTy, const clang::CallExpr *E, + CallArgList *RtlArgs); + + RValue buildCXXMemberCallExpr(const clang::CXXMemberCallExpr *E, + ReturnValueSlot ReturnValue); + RValue buildCXXMemberOrOperatorMemberCallExpr( + const clang::CallExpr *CE, const clang::CXXMethodDecl *MD, + ReturnValueSlot ReturnValue, bool HasQualifier, + clang::NestedNameSpecifier *Qualifier, bool IsArrow, + const clang::Expr *Base); + RValue buildCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *E, + const CXXMethodDecl *MD, + ReturnValueSlot ReturnValue); + void buildNullInitialization(mlir::Location loc, Address DestPtr, + QualType Ty); + bool shouldNullCheckClassCastValue(const CastExpr *CE); + + void buildCXXTemporary(const CXXTemporary *Temporary, QualType TempType, + Address Ptr); + mlir::Value buildCXXNewExpr(const CXXNewExpr *E); + + void buildDeleteCall(const FunctionDecl *DeleteFD, mlir::Value Ptr, + QualType DeleteTy, mlir::Value NumElements = nullptr, + CharUnits CookieSize = CharUnits()); + + mlir::Value createLoad(const clang::VarDecl *VD, const char *Name); + + mlir::Value buildScalarPrePostIncDec(const UnaryOperator *E, LValue LV, + bool isInc, bool isPre); + + // Wrapper for function prototype sources. Wraps either a FunctionProtoType or + // an ObjCMethodDecl. + struct PrototypeWrapper { + llvm::PointerUnion + P; + + PrototypeWrapper(const clang::FunctionProtoType *FT) : P(FT) {} + PrototypeWrapper(const clang::ObjCMethodDecl *MD) : P(MD) {} + }; + + bool LValueIsSuitableForInlineAtomic(LValue Src); + + /// An abstract representation of regular/ObjC call/message targets. + class AbstractCallee { + /// The function declaration of the callee. + const clang::Decl *CalleeDecl; + + public: + AbstractCallee() : CalleeDecl(nullptr) {} + AbstractCallee(const clang::FunctionDecl *FD) : CalleeDecl(FD) {} + AbstractCallee(const clang::ObjCMethodDecl *OMD) : CalleeDecl(OMD) {} + bool hasFunctionDecl() const { + return llvm::isa_and_nonnull(CalleeDecl); + } + const clang::Decl *getDecl() const { return CalleeDecl; } + unsigned getNumParams() const { + if (const auto *FD = llvm::dyn_cast(CalleeDecl)) + return FD->getNumParams(); + return llvm::cast(CalleeDecl)->param_size(); + } + const clang::ParmVarDecl *getParamDecl(unsigned I) const { + if (const auto *FD = llvm::dyn_cast(CalleeDecl)) + return FD->getParamDecl(I); + return *(llvm::cast(CalleeDecl)->param_begin() + + I); + } + }; + + RValue convertTempToRValue(Address addr, clang::QualType type, + clang::SourceLocation Loc); + + /// If a ParmVarDecl had the pass_object_size attribute, this + /// will contain a mapping from said ParmVarDecl to its implicit "object_size" + /// parameter. + llvm::SmallDenseMap + SizeArguments; + + // Build a "reference" to a va_list; this is either the address or the value + // of the expression, depending on how va_list is defined. + Address buildVAListRef(const Expr *E); + + /// Emits a CIR variable-argument operation, either + /// \c cir.va.start or \c cir.va.end. + /// + /// \param ArgValue A reference to the \c va_list as emitted by either + /// \c buildVAListRef or \c buildMSVAListRef. + /// + /// \param IsStart If \c true, emits \c cir.va.start, otherwise \c cir.va.end. + void buildVAStartEnd(mlir::Value ArgValue, bool IsStart); + + /// Generate code to get an argument from the passed in pointer + /// and update it accordingly. + /// + /// \param VE The \c VAArgExpr for which to generate code. + /// + /// \param VAListAddr Receives a reference to the \c va_list as emitted by + /// either \c buildVAListRef or \c buildMSVAListRef. + /// + /// \returns SSA value with the argument. + mlir::Value buildVAArg(VAArgExpr *VE, Address &VAListAddr); + + mlir::Value emitBuiltinObjectSize(const Expr *E, unsigned Type, + mlir::cir::IntType ResType, + mlir::Value EmittedE, bool IsDynamic); + mlir::Value evaluateOrEmitBuiltinObjectSize(const Expr *E, unsigned Type, + mlir::cir::IntType ResType, + mlir::Value EmittedE, + bool IsDynamic); + + /// 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 buildLoadOfLValue(LValue LV, SourceLocation Loc); + mlir::Value buildLoadOfScalar(Address Addr, bool Volatile, clang::QualType Ty, + clang::SourceLocation Loc, + LValueBaseInfo BaseInfo, + bool isNontemporal = false); + mlir::Value buildLoadOfScalar(Address Addr, bool Volatile, clang::QualType Ty, + mlir::Location Loc, LValueBaseInfo BaseInfo, + bool isNontemporal = false); + + RValue buildLoadOfBitfieldLValue(LValue LV, SourceLocation Loc); + + /// Load a scalar value from an address, taking care to appropriately convert + /// from the memory representation to CIR value representation. + mlir::Value buildLoadOfScalar(Address Addr, bool Volatile, clang::QualType Ty, + clang::SourceLocation Loc, + AlignmentSource Source = AlignmentSource::Type, + bool isNontemporal = false) { + return buildLoadOfScalar(Addr, Volatile, Ty, Loc, LValueBaseInfo(Source), + isNontemporal); + } + + /// Load a scalar value from an address, taking care to appropriately convert + /// form the memory representation to the CIR value representation. The + /// l-value must be a simple l-value. + mlir::Value buildLoadOfScalar(LValue lvalue, clang::SourceLocation Loc); + mlir::Value buildLoadOfScalar(LValue lvalue, mlir::Location Loc); + + Address buildLoadOfReference(LValue RefLVal, mlir::Location Loc, + LValueBaseInfo *PointeeBaseInfo = nullptr); + LValue buildLoadOfReferenceLValue(LValue RefLVal, mlir::Location Loc); + LValue + buildLoadOfReferenceLValue(Address RefAddr, mlir::Location Loc, + QualType RefTy, + AlignmentSource Source = AlignmentSource::Type) { + LValue RefLVal = makeAddrLValue(RefAddr, RefTy, LValueBaseInfo(Source)); + return buildLoadOfReferenceLValue(RefLVal, Loc); + } + void buildImplicitAssignmentOperatorBody(FunctionArgList &Args); + + void buildAggregateStore(mlir::Value Val, Address Dest, bool DestIsVolatile); + + void buildCallArgs( + CallArgList &Args, PrototypeWrapper Prototype, + llvm::iterator_range ArgRange, + AbstractCallee AC = AbstractCallee(), unsigned ParamsToSkip = 0, + EvaluationOrder Order = EvaluationOrder::Default); + + void checkTargetFeatures(const CallExpr *E, const FunctionDecl *TargetDecl); + void checkTargetFeatures(SourceLocation Loc, const FunctionDecl *TargetDecl); + + /// Generate a call of the given function, expecting the given + /// result type, and using the given argument list which specifies both the + /// LLVM arguments and the types they were derived from. + RValue buildCall(const CIRGenFunctionInfo &CallInfo, + const CIRGenCallee &Callee, ReturnValueSlot ReturnValue, + const CallArgList &Args, mlir::cir::CallOp *callOrInvoke, + bool IsMustTail, mlir::Location loc); + RValue buildCall(const CIRGenFunctionInfo &CallInfo, + const CIRGenCallee &Callee, ReturnValueSlot ReturnValue, + const CallArgList &Args, + mlir::cir::CallOp *callOrInvoke = nullptr, + bool IsMustTail = false) { + assert(currSrcLoc && "source location must have been set"); + return buildCall(CallInfo, Callee, ReturnValue, Args, callOrInvoke, + IsMustTail, *currSrcLoc); + } + RValue buildCall(clang::QualType FnType, const CIRGenCallee &Callee, + const clang::CallExpr *E, ReturnValueSlot returnValue, + mlir::Value Chain = nullptr); + + RValue buildCallExpr(const clang::CallExpr *E, + ReturnValueSlot ReturnValue = ReturnValueSlot()); + + /// Create a check for a function parameter that may potentially be + /// declared as non-null. + void buildNonNullArgCheck(RValue RV, QualType ArgType, SourceLocation ArgLoc, + AbstractCallee AC, unsigned ParmNum); + + void buildCallArg(CallArgList &args, const clang::Expr *E, + clang::QualType ArgType); + + LValue buildCallExprLValue(const CallExpr *E); + + /// Similarly to buildAnyExpr(), however, the result will always be accessible + /// even if no aggregate location is provided. + RValue buildAnyExprToTemp(const clang::Expr *E); + + CIRGenCallee buildCallee(const clang::Expr *E); + + /// 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 buildAnyExpr(const clang::Expr *E, + AggValueSlot aggSlot = AggValueSlot::ignored(), + bool ignoreResult = false); + + mlir::LogicalResult buildFunctionBody(const clang::Stmt *Body); + mlir::LogicalResult buildCoroutineBody(const CoroutineBodyStmt &S); + mlir::LogicalResult buildCoreturnStmt(const CoreturnStmt &S); + + mlir::cir::CallOp buildCoroIDBuiltinCall(mlir::Location loc, + mlir::Value nullPtr); + mlir::cir::CallOp buildCoroAllocBuiltinCall(mlir::Location loc); + mlir::cir::CallOp buildCoroBeginBuiltinCall(mlir::Location loc, + mlir::Value coroframeAddr); + mlir::cir::CallOp buildCoroEndBuiltinCall(mlir::Location loc, + mlir::Value nullPtr); + + RValue buildCoawaitExpr(const CoawaitExpr &E, + AggValueSlot aggSlot = AggValueSlot::ignored(), + bool ignoreResult = false); + RValue buildCoroutineIntrinsic(const CallExpr *E, unsigned int IID); + RValue buildCoroutineFrame(); + + /// Build a debug stoppoint if we are emitting debug info. + void buildStopPoint(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 buildStmt(const clang::Stmt *S, bool useCurrentScope, + ArrayRef Attrs = std::nullopt); + + mlir::LogicalResult buildSimpleStmt(const clang::Stmt *S, + bool useCurrentScope); + + mlir::LogicalResult buildForStmt(const clang::ForStmt &S); + mlir::LogicalResult buildWhileStmt(const clang::WhileStmt &S); + mlir::LogicalResult buildDoStmt(const clang::DoStmt &S); + mlir::LogicalResult + buildCXXForRangeStmt(const CXXForRangeStmt &S, + ArrayRef Attrs = std::nullopt); + mlir::LogicalResult buildSwitchStmt(const clang::SwitchStmt &S); + + mlir::LogicalResult buildCXXTryStmt(const clang::CXXTryStmt &S); + void enterCXXTryStmt(const CXXTryStmt &S, mlir::cir::CatchOp catchOp, + bool IsFnTryBlock = false); + void exitCXXTryStmt(const CXXTryStmt &S, bool IsFnTryBlock = false); + + mlir::LogicalResult buildCompoundStmt(const clang::CompoundStmt &S); + + mlir::LogicalResult + buildCompoundStmtWithoutScope(const clang::CompoundStmt &S); + + GlobalDecl CurSEHParent; + bool currentFunctionUsesSEHTry() const { return !!CurSEHParent; } + + /// Returns true inside SEH __try blocks. + bool isSEHTryScope() const { return UnimplementedFeature::isSEHTryScope(); } + + mlir::Operation *CurrentFuncletPad = nullptr; + + /// Returns true while emitting a cleanuppad. + bool isCleanupPadScope() const { + assert(!CurrentFuncletPad && "NYI"); + return false; + } + + /// Emit code to compute the specified expression, + /// ignoring the result. + void buildIgnoredExpr(const clang::Expr *E); + + LValue buildArraySubscriptExpr(const clang::ArraySubscriptExpr *E, + bool Accessed = false); + + mlir::LogicalResult buildDeclStmt(const clang::DeclStmt &S); + + /// Determine whether a return value slot may overlap some other object. + AggValueSlot::Overlap_t getOverlapForReturnValue() { + // FIXME: Assuming no overlap here breaks guaranteed copy elision for base + // class subobjects. These cases may need to be revisited depending on the + // resolution of the relevant core issue. + return AggValueSlot::DoesNotOverlap; + } + + /// Determine whether a base class initialization may overlap some other + /// object. + AggValueSlot::Overlap_t getOverlapForBaseInit(const CXXRecordDecl *RD, + const CXXRecordDecl *BaseRD, + bool IsVirtual); + + /// Get an appropriate 'undef' rvalue for the given type. + /// TODO: What's the equivalent for MLIR? Currently we're only using this for + /// void types so it just returns RValue::get(nullptr) but it'll need + /// addressed later. + RValue GetUndefRValue(clang::QualType Ty); + + mlir::Value buildFromMemory(mlir::Value Value, clang::QualType Ty); + + mlir::Type convertType(clang::QualType T); + + mlir::LogicalResult buildIfStmt(const clang::IfStmt &S); + + mlir::LogicalResult buildReturnStmt(const clang::ReturnStmt &S); + + mlir::LogicalResult buildGotoStmt(const clang::GotoStmt &S); + + mlir::LogicalResult buildLabel(const clang::LabelDecl *D); + mlir::LogicalResult buildLabelStmt(const clang::LabelStmt &S); + + mlir::LogicalResult buildBreakStmt(const clang::BreakStmt &S); + mlir::LogicalResult buildContinueStmt(const clang::ContinueStmt &S); + + LValue buildOpaqueValueLValue(const OpaqueValueExpr *e); + + /// Emit code to compute a designator that specifies the location + /// of the expression. + /// FIXME: document this function better. + LValue buildLValue(const clang::Expr *E); + + void buildDecl(const clang::Decl &D); + + /// If the specified expression does not fold to a constant, or if it does but + /// contains a label, return false. If it constant folds return true and set + /// the boolean result in Result. + bool ConstantFoldsToSimpleInteger(const clang::Expr *Cond, bool &ResultBool, + bool AllowLabels = false); + bool ConstantFoldsToSimpleInteger(const clang::Expr *Cond, + llvm::APSInt &ResultInt, + bool AllowLabels = false); + + /// Return true if the statement contains a label in it. If + /// this statement is not executed normally, it not containing a label means + /// that we can just remove the code. + bool ContainsLabel(const clang::Stmt *S, bool IgnoreCaseStmts = false); + + /// Emit an if on a boolean condition to the specified blocks. + /// FIXME: Based on the condition, this might try to simplify the codegen of + /// the conditional based on the branch. TrueCount should be the number of + /// times we expect the condition to evaluate to true based on PGO data. We + /// might decide to leave this as a separate pass (see EmitBranchOnBoolExpr + /// for extra ideas). + mlir::LogicalResult buildIfOnBoolExpr(const clang::Expr *cond, + const clang::Stmt *thenS, + const clang::Stmt *elseS); + mlir::Value buildTernaryOnBoolExpr(const clang::Expr *cond, + mlir::Location loc, + const clang::Stmt *thenS, + const clang::Stmt *elseS); + mlir::Value buildOpOnBoolExpr(const clang::Expr *cond, mlir::Location loc, + const clang::Stmt *thenS, + const clang::Stmt *elseS); + + class ConstantEmission { + // Cannot use mlir::TypedAttr directly here because of bit availability. + llvm::PointerIntPair ValueAndIsReference; + ConstantEmission(mlir::TypedAttr C, bool isReference) + : ValueAndIsReference(C, isReference) {} + + public: + ConstantEmission() {} + static ConstantEmission forReference(mlir::TypedAttr C) { + return ConstantEmission(C, true); + } + static ConstantEmission forValue(mlir::TypedAttr C) { + return ConstantEmission(C, false); + } + + explicit operator bool() const { + return ValueAndIsReference.getOpaqueValue() != nullptr; + } + + bool isReference() const { return ValueAndIsReference.getInt(); } + LValue getReferenceLValue(CIRGenFunction &CGF, Expr *refExpr) const { + assert(isReference()); + // create(loc, ty, getZeroAttr(ty)); + // CGF.getBuilder().const + // return CGF.MakeNaturalAlignAddrLValue(ValueAndIsReference.getPointer(), + // refExpr->getType()); + llvm_unreachable("NYI"); + } + + mlir::TypedAttr getValue() const { + assert(!isReference()); + return ValueAndIsReference.getPointer().cast(); + } + }; + + ConstantEmission tryEmitAsConstant(DeclRefExpr *refExpr); + ConstantEmission tryEmitAsConstant(const MemberExpr *ME); + + /// Emit the computation of the specified expression of scalar type, + /// ignoring the result. + mlir::Value buildScalarExpr(const clang::Expr *E); + mlir::Value buildScalarConstant(const ConstantEmission &Constant, Expr *E); + + mlir::Type getCIRType(const clang::QualType &type); + + const CaseStmt *foldCaseStmt(const clang::CaseStmt &S, mlir::Type condType, + SmallVector &caseAttrs); + + void insertFallthrough(const clang::Stmt &S); + + template + mlir::LogicalResult + buildCaseDefaultCascade(const T *stmt, mlir::Type condType, + SmallVector &caseAttrs, + mlir::OperationState &os); + + mlir::LogicalResult buildCaseStmt(const clang::CaseStmt &S, + mlir::Type condType, + SmallVector &caseAttrs, + mlir::OperationState &op); + + mlir::LogicalResult + buildDefaultStmt(const clang::DefaultStmt &S, mlir::Type condType, + SmallVector &caseAttrs, + mlir::OperationState &op); + + mlir::cir::FuncOp generateCode(clang::GlobalDecl GD, mlir::cir::FuncOp Fn, + const CIRGenFunctionInfo &FnInfo); + + clang::QualType buildFunctionArgList(clang::GlobalDecl GD, + FunctionArgList &Args); + 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()); } + /// 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; + + llvm_unreachable("NYI"); + } + }; + + LValue buildMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E); + + /// Emit the alloca and debug information for a + /// local variable. Does not emit initialization or destruction. + AutoVarEmission buildAutoVarAlloca(const clang::VarDecl &D); + + void buildAutoVarInit(const AutoVarEmission &emission); + void buildAutoVarCleanups(const AutoVarEmission &emission); + void buildAutoVarTypeCleanup(const AutoVarEmission &emission, + clang::QualType::DestructionKind dtorKind); + + void buildStoreOfScalar(mlir::Value value, LValue lvalue); + void buildStoreOfScalar(mlir::Value Value, Address Addr, bool Volatile, + clang::QualType Ty, LValueBaseInfo BaseInfo, + bool isInit = false, bool isNontemporal = false); + void buildStoreOfScalar(mlir::Value value, LValue lvalue, bool isInit); + + mlir::Value buildToMemory(mlir::Value Value, clang::QualType Ty); + void buildDeclRefExprDbgValue(const DeclRefExpr *E, const APValue &Init); + + /// Store the specified rvalue into the specified + /// lvalue, where both are guaranteed to the have the same type, and that type + /// is 'Ty'. + void buildStoreThroughLValue(RValue Src, LValue Dst); + + void buildStoreThroughBitfieldLValue(RValue Src, LValue Dst, + mlir::Value &Result); + + mlir::cir::BrOp buildBranchThroughCleanup(mlir::Location Loc, JumpDest Dest); + + /// Given an assignment `*LHS = RHS`, emit a test that checks if \p RHS is + /// nonnull, if 1\p LHS is marked _Nonnull. + void buildNullabilityCheck(LValue LHS, mlir::Value RHS, + clang::SourceLocation Loc); + + /// Same as IRBuilder::CreateInBoundsGEP, but additionally emits a check to + /// detect undefined behavior when the pointer overflow sanitizer is enabled. + /// \p SignedIndices indicates whether any of the GEP indices are signed. + /// \p IsSubtraction indicates whether the expression used to form the GEP + /// is a subtraction. + mlir::Value buildCheckedInBoundsGEP(mlir::Type ElemTy, mlir::Value Ptr, + ArrayRef IdxList, + bool SignedIndices, bool IsSubtraction, + SourceLocation Loc); + + void buildScalarInit(const clang::Expr *init, mlir::Location loc, + LValue lvalue, bool capturedByInit = false); + + LValue buildDeclRefLValue(const clang::DeclRefExpr *E); + LValue buildBinaryOperatorLValue(const clang::BinaryOperator *E); + LValue buildCompoundAssignmentLValue(const clang::CompoundAssignOperator *E); + LValue buildUnaryOpLValue(const clang::UnaryOperator *E); + LValue buildStringLiteralLValue(const StringLiteral *E); + RValue buildBuiltinExpr(const clang::GlobalDecl GD, unsigned BuiltinID, + const clang::CallExpr *E, + ReturnValueSlot ReturnValue); + mlir::Value buildTargetBuiltinExpr(unsigned BuiltinID, + const clang::CallExpr *E, + ReturnValueSlot ReturnValue); + + /// Given an expression with a pointer type, emit the value and compute our + /// best estimate of the alignment of the pointee. + /// + /// \param BaseInfo - If non-null, this will be initialized with + /// information about the source of the alignment and the may-alias + /// attribute. Note that this function will conservatively fall back on + /// the type when it doesn't recognize the expression and may-alias will + /// be set to false. + /// + /// One reasonable way to use this information is when there's a language + /// guarantee that the pointer must be aligned to some stricter value, and + /// we're simply trying to ensure that sufficiently obvious uses of under- + /// aligned objects don't get miscompiled; for example, a placement new + /// into the address of a local variable. In such a case, it's quite + /// reasonable to just ignore the returned alignment when it isn't from an + /// explicit source. + Address + buildPointerWithAlignment(const clang::Expr *E, + LValueBaseInfo *BaseInfo = nullptr, + KnownNonNull_t IsKnownNonNull = NotKnownNonNull); + + LValue + buildConditionalOperatorLValue(const AbstractConditionalOperator *expr); + + /// 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 buildExprAsInit(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 buildAutoVarDecl(const clang::VarDecl &D); + + /// This method handles emission of any variable declaration + /// inside a function, including static vars etc. + void buildVarDecl(const clang::VarDecl &D); + + mlir::cir::GlobalOp addInitializerToStaticVarDecl(const VarDecl &D, + mlir::cir::GlobalOp GV); + + void buildStaticVarDecl(const VarDecl &D, + mlir::cir::GlobalLinkageKind Linkage); + + /// 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); + + void buildCtorPrologue(const clang::CXXConstructorDecl *CD, + clang::CXXCtorType Type, FunctionArgList &Args); + void buildConstructorBody(FunctionArgList &Args); + void buildDestructorBody(FunctionArgList &Args); + void buildCXXDestructorCall(const CXXDestructorDecl *D, CXXDtorType Type, + bool ForVirtualBase, bool Delegating, + Address This, QualType ThisTy); + RValue buildCXXDestructorCall(GlobalDecl Dtor, const CIRGenCallee &Callee, + mlir::Value This, QualType ThisTy, + mlir::Value ImplicitParam, + QualType ImplicitParamTy, const CallExpr *E); + + /// Enter the cleanups necessary to complete the given phase of destruction + /// for a destructor. The end result should call destructors on members and + /// base classes in reverse order of their construction. + void EnterDtorCleanups(const CXXDestructorDecl *Dtor, CXXDtorType Type); + + /// Determines whether an EH cleanup is required to destroy a type + /// with the given destruction kind. + /// TODO(cir): could be shared with Clang LLVM codegen + bool needsEHCleanup(QualType::DestructionKind kind) { + switch (kind) { + case QualType::DK_none: + return false; + case QualType::DK_cxx_destructor: + case QualType::DK_objc_weak_lifetime: + case QualType::DK_nontrivial_c_struct: + return getLangOpts().Exceptions; + case QualType::DK_objc_strong_lifetime: + return getLangOpts().Exceptions && + CGM.getCodeGenOpts().ObjCAutoRefCountExceptions; + } + llvm_unreachable("bad destruction kind"); + } + + void pushEHDestroy(QualType::DestructionKind dtorKind, Address addr, + QualType type); + + static bool + IsConstructorDelegationValid(const clang::CXXConstructorDecl *Ctor); + + struct VPtr { + clang::BaseSubobject Base; + const clang::CXXRecordDecl *NearestVBase; + clang::CharUnits OffsetFromNearestVBase; + const clang::CXXRecordDecl *VTableClass; + }; + + using VisitedVirtualBasesSetTy = + llvm::SmallPtrSet; + + using VPtrsVector = llvm::SmallVector; + VPtrsVector getVTablePointers(const clang::CXXRecordDecl *VTableClass); + void getVTablePointers(clang::BaseSubobject Base, + const clang::CXXRecordDecl *NearestVBase, + clang::CharUnits OffsetFromNearestVBase, + bool BaseIsNonVirtualPrimaryBase, + const clang::CXXRecordDecl *VTableClass, + VisitedVirtualBasesSetTy &VBases, VPtrsVector &vptrs); + /// Return the Value of the vtable pointer member pointed to by This. + mlir::Value getVTablePtr(SourceLocation Loc, Address This, + mlir::Type VTableTy, + const CXXRecordDecl *VTableClass); + + /// Returns whether we should perform a type checked load when loading a + /// virtual function for virtual calls to members of RD. This is generally + /// true when both vcall CFI and whole-program-vtables are enabled. + bool shouldEmitVTableTypeCheckedLoad(const CXXRecordDecl *RD); + + /// If whole-program virtual table optimization is enabled, emit an assumption + /// that VTable is a member of RD's type identifier. Or, if vptr CFI is + /// enabled, emit a check that VTable is a member of RD's type identifier. + void buildTypeMetadataCodeForVCall(const CXXRecordDecl *RD, + mlir::Value VTable, SourceLocation Loc); + + /// Return the VTT parameter that should be passed to a base + /// constructor/destructor with virtual bases. + /// FIXME: VTTs are Itanium ABI-specific, so the definition should move + /// to CIRGenItaniumCXXABI.cpp together with all the references to VTT. + mlir::Value GetVTTParameter(GlobalDecl GD, bool ForVirtualBase, + bool Delegating); + + /// Source location information about the default argument or member + /// initializer expression we're evaluating, if any. + clang::CurrentSourceLocExprScope CurSourceLocExprScope; + using SourceLocExprScopeGuard = + clang::CurrentSourceLocExprScope::SourceLocExprScopeGuard; + + /// A scoep within which we are constructing the fields of an object which + /// might use a CXXDefaultInitExpr. This stashes away a 'this' value to use if + /// we need to evaluate the CXXDefaultInitExpr within the evaluation. + class FieldConstructionScope { + public: + FieldConstructionScope(CIRGenFunction &CGF, Address This) + : CGF(CGF), OldCXXDefaultInitExprThis(CGF.CXXDefaultInitExprThis) { + CGF.CXXDefaultInitExprThis = This; + } + ~FieldConstructionScope() { + CGF.CXXDefaultInitExprThis = OldCXXDefaultInitExprThis; + } + + private: + CIRGenFunction &CGF; + Address OldCXXDefaultInitExprThis; + }; + + /// The scope of a CXXDefaultInitExpr. Within this scope, the value of 'this' + /// is overridden to be the object under construction. + class CXXDefaultInitExprScope { + public: + CXXDefaultInitExprScope(CIRGenFunction &CGF, + const clang::CXXDefaultInitExpr *E) + : CGF{CGF}, OldCXXThisValue(CGF.CXXThisValue), + OldCXXThisAlignment(CGF.CXXThisAlignment), + SourceLocScope(E, CGF.CurSourceLocExprScope) { + CGF.CXXThisValue = CGF.CXXDefaultInitExprThis.getPointer(); + CGF.CXXThisAlignment = CGF.CXXDefaultInitExprThis.getAlignment(); + } + ~CXXDefaultInitExprScope() { + CGF.CXXThisValue = OldCXXThisValue; + CGF.CXXThisAlignment = OldCXXThisAlignment; + } + + public: + CIRGenFunction &CGF; + mlir::Value OldCXXThisValue; + clang::CharUnits OldCXXThisAlignment; + SourceLocExprScopeGuard SourceLocScope; + }; + + struct CXXDefaultArgExprScope : SourceLocExprScopeGuard { + CXXDefaultArgExprScope(CIRGenFunction &CGF, const CXXDefaultArgExpr *E) + : SourceLocExprScopeGuard(E, CGF.CurSourceLocExprScope) {} + }; + + LValue MakeNaturalAlignPointeeAddrLValue(mlir::Value V, clang::QualType T); + LValue MakeNaturalAlignAddrLValue(mlir::Value V, QualType T); + + /// Load the value for 'this'. This function is only valid while generating + /// code for an C++ member function. + /// FIXME(cir): this should return a mlir::Value! + mlir::Value LoadCXXThis() { + assert(CXXThisValue && "no 'this' value for this function"); + return CXXThisValue; + } + Address LoadCXXThisAddress(); + + /// Convert the given pointer to a complete class to the given direct base. + Address getAddressOfDirectBaseInCompleteClass(mlir::Location loc, + Address Value, + const CXXRecordDecl *Derived, + const CXXRecordDecl *Base, + bool BaseIsVirtual); + + Address getAddressOfBaseClass(Address Value, const CXXRecordDecl *Derived, + CastExpr::path_const_iterator PathBegin, + CastExpr::path_const_iterator PathEnd, + bool NullCheckValue, SourceLocation Loc); + + /// 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 RetTy, + mlir::cir::FuncOp Fn, const CIRGenFunctionInfo &FnInfo, + const FunctionArgList &Args, clang::SourceLocation Loc, + clang::SourceLocation StartLoc); + + /// Emit a conversion from the specified type to the specified destination + /// type, both of which are CIR scalar types. + mlir::Value buildScalarConversion(mlir::Value Src, clang::QualType SrcTy, + clang::QualType DstTy, + clang::SourceLocation Loc); + + LValue makeAddrLValue(Address Addr, clang::QualType T, + LValueBaseInfo BaseInfo) { + return LValue::makeAddr(Addr, T, getContext(), BaseInfo); + } + + LValue makeAddrLValue(Address Addr, clang::QualType T, + AlignmentSource Source = AlignmentSource::Type) { + return LValue::makeAddr(Addr, T, getContext(), LValueBaseInfo(Source)); + } + + void initializeVTablePointers(mlir::Location loc, + const clang::CXXRecordDecl *RD); + void initializeVTablePointer(mlir::Location loc, const VPtr &Vptr); + + AggValueSlot::Overlap_t getOverlapForFieldInit(const FieldDecl *FD); + LValue buildLValueForField(LValue Base, const clang::FieldDecl *Field); + LValue buildLValueForBitField(LValue base, const FieldDecl *field); + + /// Like buildLValueForField, excpet that if the Field is a reference, this + /// will return the address of the reference and not the address of the value + /// stored in the reference. + LValue buildLValueForFieldInitialization(LValue Base, + const clang::FieldDecl *Field, + llvm::StringRef FieldName); + + void buildInitializerForField(clang::FieldDecl *Field, LValue LHS, + clang::Expr *Init); + + /// Determine whether the given initializer is trivial in the sense + /// that it requires no code to be generated. + bool isTrivialInitializer(const clang::Expr *Init); + + // TODO: this can also be abstrated into common AST helpers + bool hasBooleanRepresentation(clang::QualType Ty); + + void buildCXXThrowExpr(const CXXThrowExpr *E); + + /// Return the address of a local variable. + Address GetAddrOfLocalVar(const clang::VarDecl *VD) { + auto it = LocalDeclMap.find(VD); + assert(it != LocalDeclMap.end() && + "Invalid argument to GetAddrOfLocalVar(), no decl!"); + return it->second; + } + + Address getAddrOfBitFieldStorage(LValue base, const clang::FieldDecl *field, + unsigned index, unsigned size); + + /// Given an opaque value expression, return its LValue mapping if it exists, + /// otherwise create one. + LValue getOrCreateOpaqueLValueMapping(const OpaqueValueExpr *e); + + /// Given an opaque value expression, return its RValue mapping if it exists, + /// otherwise create one. + RValue getOrCreateOpaqueRValueMapping(const OpaqueValueExpr *e); + + /// Check if \p E is a C++ "this" pointer wrapped in value-preserving casts. + static bool isWrappedCXXThis(const clang::Expr *E); + + void buildDelegateCXXConstructorCall(const clang::CXXConstructorDecl *Ctor, + clang::CXXCtorType CtorType, + const FunctionArgList &Args, + clang::SourceLocation Loc); + + /// We are performing a delegate call; that is, the current function is + /// delegating to another one. Produce a r-value suitable for passing the + /// given parameter. + void buildDelegateCallArg(CallArgList &args, const clang::VarDecl *param, + clang::SourceLocation loc); + + /// Return true if the current function should be instrumented with + /// __cyg_profile_func_* calls + bool ShouldInstrumentFunction(); + + /// TODO(cir): add TBAAAccessInfo + Address buildArrayToPointerDecay(const Expr *Array, + LValueBaseInfo *BaseInfo = nullptr); + + /// Emits the code necessary to evaluate an arbitrary expression into the + /// given memory location. + void buildAnyExprToMem(const Expr *E, Address Location, Qualifiers Quals, + bool IsInitializer); + void buildAnyExprToExn(const Expr *E, Address Addr); + + LValue buildCheckedLValue(const Expr *E, TypeCheckKind TCK); + LValue buildMemberExpr(const MemberExpr *E); + + /// returns true if aggregate type has a volatile member. + /// TODO(cir): this could be a common AST helper between LLVM / CIR. + bool hasVolatileMember(QualType T) { + if (const RecordType *RT = T->getAs()) { + const RecordDecl *RD = cast(RT->getDecl()); + return RD->hasVolatileMember(); + } + return false; + } + + /// Emit an aggregate assignment. + void buildAggregateAssign(LValue Dest, LValue Src, QualType EltTy) { + bool IsVolatile = hasVolatileMember(EltTy); + buildAggregateCopy(Dest, Src, EltTy, AggValueSlot::MayOverlap, IsVolatile); + } + + /// Emit an aggregate copy. + /// + /// \param isVolatile \c true iff either the source or the destination is + /// volatile. + /// \param MayOverlap Whether the tail padding of the destination might be + /// occupied by some other object. More efficient code can often be + /// generated if not. + void buildAggregateCopy(LValue Dest, LValue Src, QualType EltTy, + AggValueSlot::Overlap_t MayOverlap, + bool isVolatile = false); + + /// + /// Cleanups + /// -------- + + /// Header for data within LifetimeExtendedCleanupStack. + struct LifetimeExtendedCleanupHeader { + /// The size of the following cleanup object. + unsigned Size; + /// The kind of cleanup to push: a value from the CleanupKind enumeration. + unsigned Kind : 31; + /// Whether this is a conditional cleanup. + unsigned IsConditional : 1; + + size_t getSize() const { return Size; } + CleanupKind getKind() const { return (CleanupKind)Kind; } + bool isConditional() const { return IsConditional; } + }; + + /// Emits landing pad information for the current EH stack. + mlir::Block *buildLandingPad(); + + // TODO(cir): perhaps return a mlir::Block* here, for now + // only check if a landing pad is required. + mlir::Block *getInvokeDestImpl(); + bool getInvokeDest() { + if (!EHStack.requiresLandingPad()) + return false; + return (bool)getInvokeDestImpl(); + } + + /// Takes the old cleanup stack size and emits the cleanup blocks + /// that have been added. + void + PopCleanupBlocks(EHScopeStack::stable_iterator OldCleanupStackSize, + std::initializer_list ValuesToReload = {}); + + /// Takes the old cleanup stack size and emits the cleanup blocks + /// that have been added, then adds all lifetime-extended cleanups from + /// the given position to the stack. + void + PopCleanupBlocks(EHScopeStack::stable_iterator OldCleanupStackSize, + size_t OldLifetimeExtendedStackSize, + std::initializer_list ValuesToReload = {}); + + /// Will pop the cleanup entry on the stack and process all branch fixups. + void PopCleanupBlock(bool FallThroughIsBranchThrough = false); + + /// Deactivates the given cleanup block. The block cannot be reactivated. Pops + /// it if it's the top of the stack. + /// + /// \param DominatingIP - An instruction which is known to + /// dominate the current IP (if set) and which lies along + /// all paths of execution between the current IP and the + /// the point at which the cleanup comes into scope. + void DeactivateCleanupBlock(EHScopeStack::stable_iterator Cleanup, + mlir::Operation *DominatingIP); + + typedef void Destroyer(CIRGenFunction &CGF, Address addr, QualType ty); + + static Destroyer destroyCXXObject; + + void pushDestroy(CleanupKind kind, Address addr, QualType type, + Destroyer *destroyer, bool useEHCleanupForArray); + + Destroyer *getDestroyer(QualType::DestructionKind kind); + + /// An object to manage conditionally-evaluated expressions. + class ConditionalEvaluation { + // llvm::BasicBlock *StartBB; + + public: + ConditionalEvaluation(CIRGenFunction &CGF) + /*: StartBB(CGF.Builder.GetInsertBlock())*/ {} + + void begin(CIRGenFunction &CGF) { + assert(CGF.OutermostConditional != this); + if (!CGF.OutermostConditional) + CGF.OutermostConditional = this; + } + + void end(CIRGenFunction &CGF) { + assert(CGF.OutermostConditional != nullptr); + if (CGF.OutermostConditional == this) + CGF.OutermostConditional = nullptr; + } + + /// Returns a block which will be executed prior to each + /// evaluation of the conditional code. + // llvm::BasicBlock *getStartingBlock() const { return StartBB; } + }; + + struct ConditionalInfo { + std::optional LHS{}, RHS{}; + mlir::Value Result{}; + }; + + template + ConditionalInfo buildConditionalBlocks(const AbstractConditionalOperator *E, + const FuncTy &BranchGenFunc); + + // Return true if we're currently emitting one branch or the other of a + // conditional expression. + bool isInConditionalBranch() const { return OutermostConditional != nullptr; } + + void setBeforeOutermostConditional(mlir::Value value, Address addr) { + assert(isInConditionalBranch()); + llvm_unreachable("NYI"); + } + + // Points to the outermost active conditional control. This is used so that + // we know if a temporary should be destroyed conditionally. + ConditionalEvaluation *OutermostConditional = nullptr; + + /// Push a cleanup to be run at the end of the current full-expression. Safe + /// against the possibility that we're currently inside a + /// conditionally-evaluated expression. + template + void pushFullExprCleanup(CleanupKind kind, As... A) { + // If we're not in a conditional branch, or if none of the + // arguments requires saving, then use the unconditional cleanup. + if (!isInConditionalBranch()) + return EHStack.pushCleanup(kind, A...); + + llvm_unreachable("NYI"); + // Stash values in a tuple so we can guarantee the order of saves. + // typedef std::tuple::saved_type...> + // SavedTuple; SavedTuple Saved{saveValueInCond(A)...}; + + // typedef EHScopeStack::ConditionalCleanup CleanupType; + // EHStack.pushCleanupTuple(kind, Saved); + // initFullExprCleanup(); + } + + /// Set up the last cleanup that was pushed as a conditional + /// full-expression cleanup. + void initFullExprCleanup() { + initFullExprCleanupWithFlag(createCleanupActiveFlag()); + } + + void initFullExprCleanupWithFlag(Address ActiveFlag); + Address createCleanupActiveFlag(); + + /// Enters a new scope for capturing cleanups, all of which + /// will be executed once the scope is exited. + class RunCleanupsScope { + EHScopeStack::stable_iterator CleanupStackDepth, OldCleanupScopeDepth; + size_t LifetimeExtendedCleanupStackSize; + bool OldDidCallStackSave; + + protected: + bool PerformCleanup; + + private: + RunCleanupsScope(const RunCleanupsScope &) = delete; + void operator=(const RunCleanupsScope &) = delete; + + protected: + CIRGenFunction &CGF; + + public: + /// Enter a new cleanup scope. + explicit RunCleanupsScope(CIRGenFunction &CGF) + : PerformCleanup(true), CGF(CGF) { + CleanupStackDepth = CGF.EHStack.stable_begin(); + LifetimeExtendedCleanupStackSize = + CGF.LifetimeExtendedCleanupStack.size(); + OldDidCallStackSave = CGF.DidCallStackSave; + CGF.DidCallStackSave = false; + OldCleanupScopeDepth = CGF.CurrentCleanupScopeDepth; + CGF.CurrentCleanupScopeDepth = CleanupStackDepth; + } + + /// Exit this cleanup scope, emitting any accumulated cleanups. + ~RunCleanupsScope() { + if (PerformCleanup) + ForceCleanup(); + } + + /// Determine whether this scope requires any cleanups. + bool requiresCleanups() const { + return CGF.EHStack.stable_begin() != CleanupStackDepth; + } + + /// Force the emission of cleanups now, instead of waiting + /// until this object is destroyed. + /// \param ValuesToReload - A list of values that need to be available at + /// the insertion point after cleanup emission. If cleanup emission created + /// a shared cleanup block, these value pointers will be rewritten. + /// Otherwise, they not will be modified. + void + ForceCleanup(std::initializer_list ValuesToReload = {}) { + assert(PerformCleanup && "Already forced cleanup"); + CGF.DidCallStackSave = OldDidCallStackSave; + CGF.PopCleanupBlocks(CleanupStackDepth, LifetimeExtendedCleanupStackSize, + ValuesToReload); + PerformCleanup = false; + CGF.CurrentCleanupScopeDepth = OldCleanupScopeDepth; + } + }; + + // Cleanup stack depth of the RunCleanupsScope that was pushed most recently. + EHScopeStack::stable_iterator CurrentCleanupScopeDepth = + EHScopeStack::stable_end(); + + /// CIR build helpers + /// ----------------- + + /// This creates an alloca and inserts it into the entry block if \p ArraySize + /// is nullptr, + /// + /// TODO(cir): ... otherwise inserts it at the current insertion point of + /// the builder. + /// The caller is responsible for setting an appropriate alignment on + /// the alloca. + /// + /// \p ArraySize is the number of array elements to be allocated if it + /// is not nullptr. + /// + /// LangAS::Default is the address space of pointers to local variables and + /// temporaries, as exposed in the source language. In certain + /// configurations, this is not the same as the alloca address space, and a + /// cast is needed to lift the pointer from the alloca AS into + /// LangAS::Default. This can happen when the target uses a restricted + /// address space for the stack but the source language requires + /// LangAS::Default to be a generic address space. The latter condition is + /// common for most programming languages; OpenCL is an exception in that + /// LangAS::Default is the private address space, which naturally maps + /// to the stack. + /// + /// Because the address of a temporary is often exposed to the program in + /// various ways, this function will perform the cast. The original alloca + /// instruction is returned through \p Alloca if it is not nullptr. + /// + /// The cast is not performaed in CreateTempAllocaWithoutCast. This is + /// more efficient if the caller knows that the address will not be exposed. + mlir::cir::AllocaOp CreateTempAlloca(mlir::Type Ty, mlir::Location Loc, + const Twine &Name = "tmp", + mlir::Value ArraySize = nullptr, + bool insertIntoFnEntryBlock = false); + mlir::cir::AllocaOp + CreateTempAllocaInFnEntryBlock(mlir::Type Ty, mlir::Location Loc, + const Twine &Name = "tmp", + mlir::Value ArraySize = nullptr); + Address CreateTempAlloca(mlir::Type Ty, CharUnits align, mlir::Location Loc, + const Twine &Name = "tmp", + mlir::Value ArraySize = nullptr, + Address *Alloca = nullptr); + Address CreateTempAllocaWithoutCast(mlir::Type Ty, CharUnits align, + mlir::Location Loc, + const Twine &Name = "tmp", + mlir::Value ArraySize = nullptr); + + /// Create a temporary memory object of the given type, with + /// appropriate alignmen and cast it to the default address space. Returns + /// the original alloca instruction by \p Alloca if it is not nullptr. + Address CreateMemTemp(QualType T, mlir::Location Loc, + const Twine &Name = "tmp", Address *Alloca = nullptr); + Address CreateMemTemp(QualType T, CharUnits Align, mlir::Location Loc, + const Twine &Name = "tmp", Address *Alloca = nullptr); + + /// Create a temporary memory object of the given type, with + /// appropriate alignment without casting it to the default address space. + Address CreateMemTempWithoutCast(QualType T, mlir::Location Loc, + const Twine &Name = "tmp"); + Address CreateMemTempWithoutCast(QualType T, CharUnits Align, + mlir::Location Loc, + const Twine &Name = "tmp"); + + /// Create a temporary memory object for the given + /// aggregate type. + AggValueSlot CreateAggTemp(QualType T, mlir::Location Loc, + const Twine &Name = "tmp", + Address *Alloca = nullptr) { + return AggValueSlot::forAddr( + CreateMemTemp(T, Loc, Name, Alloca), T.getQualifiers(), + AggValueSlot::IsNotDestructed, AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased, AggValueSlot::DoesNotOverlap); + } + +private: + QualType getVarArgType(const Expr *Arg); +}; + +/// A specialization of DominatingValue for RValue. +template <> struct DominatingValue { + typedef RValue type; + class saved_type { + enum Kind { + ScalarLiteral, + ScalarAddress, + AggregateLiteral, + AggregateAddress, + ComplexAddress + }; + + llvm::Value *Value; + llvm::Type *ElementType; + unsigned K : 3; + unsigned Align : 29; + saved_type(llvm::Value *v, llvm::Type *e, Kind k, unsigned a = 0) + : Value(v), ElementType(e), K(k), Align(a) {} + + public: + static bool needsSaving(RValue value); + static saved_type save(CIRGenFunction &CGF, RValue value); + RValue restore(CIRGenFunction &CGF) { llvm_unreachable("NYI"); } + + // implementations in CGCleanup.cpp + }; + + static bool needsSaving(type value) { return saved_type::needsSaving(value); } + static saved_type save(CIRGenFunction &CGF, type value) { + return saved_type::save(CGF, value); + } + static type restore(CIRGenFunction &CGF, saved_type value) { + return value.restore(CGF); + } +}; + +} // namespace cir + +#endif // LLVM_CLANG_LIB_CIR_CIRGENFUNCTION_H diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h new file mode 100644 index 000000000000..36425beb9fb5 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h @@ -0,0 +1,476 @@ +//==-- CIRGenFunctionInfo.h - Representation of fn argument/return types ---==// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Defines CIRGenFunctionInfo and associated types used in representing the +// CIR source types and ABI-coerced types for function arguments and +// return values. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CIR_CIRGENFUNCTIONINFO_H +#define LLVM_CLANG_CIR_CIRGENFUNCTIONINFO_H + +#include "clang/AST/CanonicalType.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/ADT/FoldingSet.h" +#include "llvm/Support/TrailingObjects.h" + +namespace cir { + +/// ABIArgInfo - Helper class to encapsulate information about how a specific C +/// type should be passed to or returned from a function. +class ABIArgInfo { +public: + enum Kind : uint8_t { + /// Direct - Pass the argument directly using the normal converted CIR type, + /// or by coercing to another specified type stored in 'CoerceToType'). If + /// an offset is specified (in UIntData), then the argument passed is offset + /// by some number of bytes in the memory representation. A dummy argument + /// is emitted before the real argument if the specified type stored in + /// "PaddingType" is not zero. + Direct, + + /// Extend - Valid only for integer argument types. Same as 'direct' but + /// also emit a zer/sign extension attribute. + Extend, + + /// Indirect - Pass the argument indirectly via a hidden pointer with the + /// specified alignment (0 indicates default alignment) and address space. + Indirect, + + /// IndirectAliased - Similar to Indirect, but the pointer may be to an + /// object that is otherwise referenced. The object is known to not be + /// modified through any other references for the duration of the call, and + /// the callee must not itself modify the object. Because C allows parameter + /// variables to be modified and guarantees that they have unique addresses, + /// the callee must defensively copy the object into a local variable if it + /// might be modified or its address might be compared. Since those are + /// uncommon, in principle this convention allows programs to avoid copies + /// in more situations. However, it may introduce *extra* copies if the + /// callee fails to prove that a copy is unnecessary and the caller + /// naturally produces an unaliased object for the argument. + IndirectAliased, + + /// Ignore - Ignore the argument (treat as void). Useful for void and empty + /// structs. + Ignore, + + /// Expand - Only valid for aggregate argument types. The structure should + /// be expanded into consecutive arguments for its constituent fields. + /// Currently expand is only allowed on structures whose fields are all + /// scalar types or are themselves expandable types. + Expand, + + /// CoerceAndExpand - Only valid for aggregate argument types. The structure + /// should be expanded into consecutive arguments corresponding to the + /// non-array elements of the type stored in CoerceToType. + /// Array elements in the type are assumed to be padding and skipped. + CoerceAndExpand, + + // TODO: translate this idea to CIR! Define it for now just to ensure that + // we can assert it not being used + InAlloca, + KindFirst = Direct, + KindLast = InAlloca + }; + +private: + mlir::Type TypeData; // canHaveCoerceToType(); + union { + mlir::Type PaddingType; // canHavePaddingType() + mlir::Type UnpaddedCoerceAndExpandType; // isCoerceAndExpand() + }; + struct DirectAttrInfo { + unsigned Offset; + unsigned Align; + }; + struct IndirectAttrInfo { + unsigned Align; + unsigned AddrSpace; + }; + union { + DirectAttrInfo DirectAttr; // isDirect() || isExtend() + IndirectAttrInfo IndirectAttr; // isIndirect() + unsigned AllocaFieldIndex; // isInAlloca() + }; + Kind TheKind; + bool CanBeFlattened : 1; // isDirect() + bool SignExt : 1; // isExtend() + + bool canHavePaddingType() const { + return isDirect() || isExtend() || isIndirect() || isIndirectAliased() || + isExpand(); + } + + void setPaddingType(mlir::Type T) { + assert(canHavePaddingType()); + PaddingType = T; + } + +public: + ABIArgInfo(Kind K = Direct) + : TypeData(nullptr), PaddingType(nullptr), DirectAttr{0, 0}, TheKind(K), + CanBeFlattened(false) {} + + static ABIArgInfo getDirect(mlir::Type T = nullptr, unsigned Offset = 0, + mlir::Type Padding = nullptr, + bool CanBeFlattened = true, unsigned Align = 0) { + auto AI = ABIArgInfo(Direct); + AI.setCoerceToType(T); + AI.setPaddingType(Padding); + AI.setDirectOffset(Offset); + AI.setDirectAlign(Align); + AI.setCanBeFlattened(CanBeFlattened); + return AI; + } + + static ABIArgInfo getSignExtend(clang::QualType Ty, mlir::Type T = nullptr) { + assert(Ty->isIntegralOrEnumerationType() && "Unexpected QualType"); + auto AI = ABIArgInfo(Extend); + AI.setCoerceToType(T); + AI.setPaddingType(nullptr); + AI.setDirectOffset(0); + AI.setDirectAlign(0); + AI.setSignExt(true); + return AI; + } + + static ABIArgInfo getZeroExtend(clang::QualType Ty, mlir::Type T = nullptr) { + assert(Ty->isIntegralOrEnumerationType() && "Unexpected QualType"); + auto AI = ABIArgInfo(Extend); + AI.setCoerceToType(T); + AI.setPaddingType(nullptr); + AI.setDirectOffset(0); + AI.setDirectAlign(0); + AI.setSignExt(false); + return AI; + } + + // ABIArgInfo will record the argument as being extended based on the sign of + // it's type. + static ABIArgInfo getExtend(clang::QualType Ty, mlir::Type T = nullptr) { + assert(Ty->isIntegralOrEnumerationType() && "Unexpected QualType"); + if (Ty->hasSignedIntegerRepresentation()) + return getSignExtend(Ty, T); + return getZeroExtend(Ty, T); + } + + static ABIArgInfo getIgnore() { return ABIArgInfo(Ignore); } + + Kind getKind() const { return TheKind; } + bool isDirect() const { return TheKind == Direct; } + bool isInAlloca() const { return TheKind == InAlloca; } + bool isExtend() const { return TheKind == Extend; } + bool isIndirect() const { return TheKind == Indirect; } + bool isIndirectAliased() const { return TheKind == IndirectAliased; } + bool isExpand() const { return TheKind == Expand; } + bool isCoerceAndExpand() const { return TheKind == CoerceAndExpand; } + + bool canHaveCoerceToType() const { + return isDirect() || isExtend() || isCoerceAndExpand(); + } + + // Direct/Extend accessors + unsigned getDirectOffset() const { + assert((isDirect() || isExtend()) && "Not a direct or extend kind"); + return DirectAttr.Offset; + } + + void setDirectOffset(unsigned Offset) { + assert((isDirect() || isExtend()) && "Not a direct or extend kind"); + DirectAttr.Offset = Offset; + } + + void setDirectAlign(unsigned Align) { + assert((isDirect() || isExtend()) && "Not a direct or extend kind"); + DirectAttr.Align = Align; + } + + void setSignExt(bool SExt) { + assert(isExtend() && "Invalid kind!"); + SignExt = SExt; + } + + void setCanBeFlattened(bool Flatten) { + assert(isDirect() && "Invalid kind!"); + CanBeFlattened = Flatten; + } + + bool getCanBeFlattened() const { + assert(isDirect() && "Invalid kind!"); + return CanBeFlattened; + } + + mlir::Type getPaddingType() const { + return (canHavePaddingType() ? PaddingType : nullptr); + } + + mlir::Type getCoerceToType() const { + assert(canHaveCoerceToType() && "Invalid kind!"); + return TypeData; + } + + void setCoerceToType(mlir::Type T) { + assert(canHaveCoerceToType() && "Invalid kind!"); + TypeData = T; + } +}; + +struct CIRGenFunctionInfoArgInfo { + clang::CanQualType type; + ABIArgInfo info; +}; + +/// A class for recording the number of arguments that a function signature +/// requires. +class RequiredArgs { + /// The number of required arguments, or ~0 if the signature does not permit + /// optional arguments. + unsigned NumRequired; + +public: + enum All_t { All }; + + RequiredArgs(All_t _) : NumRequired(~0U) {} + explicit RequiredArgs(unsigned n) : NumRequired(n) { assert(n != ~0U); } + + unsigned getOpaqueData() const { return NumRequired; } + + bool allowsOptionalArgs() const { return NumRequired != ~0U; } + + /// Compute the arguments required by the given formal prototype, given that + /// there may be some additional, non-formal arguments in play. + /// + /// If FD is not null, this will consider pass_object_size params in FD. + static RequiredArgs + forPrototypePlus(const clang::FunctionProtoType *prototype, + unsigned additional) { + if (!prototype->isVariadic()) + return All; + + if (prototype->hasExtParameterInfos()) + additional += llvm::count_if( + prototype->getExtParameterInfos(), + [](const clang::FunctionProtoType::ExtParameterInfo &ExtInfo) { + return ExtInfo.hasPassObjectSize(); + }); + + return RequiredArgs(prototype->getNumParams() + additional); + } + + static RequiredArgs + forPrototypePlus(clang::CanQual prototype, + unsigned additional) { + return forPrototypePlus(prototype.getTypePtr(), additional); + } + + unsigned getNumRequiredArgs() const { + assert(allowsOptionalArgs()); + return NumRequired; + } +}; + +class CIRGenFunctionInfo final + : public llvm::FoldingSetNode, + private llvm::TrailingObjects< + CIRGenFunctionInfo, CIRGenFunctionInfoArgInfo, + clang::FunctionProtoType::ExtParameterInfo> { + + typedef CIRGenFunctionInfoArgInfo ArgInfo; + typedef clang::FunctionProtoType::ExtParameterInfo ExtParameterInfo; + + /// The cir::CallingConv to use for this function (as specified by the user). + unsigned CallingConvention : 8; + + /// The cir::CallingConv to actually use for this function, which may depend + /// on the ABI. + unsigned EffectiveCallingConvention : 8; + + /// The clang::CallingConv that this was originally created with. + unsigned ASTCallingConvention : 6; + + /// Whether this is an instance method. + unsigned InstanceMethod : 1; + + /// Whether this is a chain call. + unsigned ChainCall : 1; + + /// Whether this function is a CMSE nonsecure call + unsigned CmseNSCall : 1; + + /// Whether this function is noreturn. + unsigned NoReturn : 1; + + /// Whether this function is returns-retained. + unsigned ReturnsRetained : 1; + + /// Whether this function saved caller registers. + unsigned NoCallerSavedRegs : 1; + + /// How many arguments to pass inreg. + unsigned HasRegParm : 1; + unsigned RegParm : 3; + + /// Whether this function has nocf_check attribute. + unsigned NoCfCheck : 1; + + RequiredArgs Required; + + /// The struct representing all arguments passed in memory. Only used when + /// passing non-trivial types with inalloca. Not part of the profile. + /// TODO: think about modeling this properly, this is just a dumb subsitution + /// for now since we arent supporting anything other than arguments in + /// registers atm + mlir::cir::StructType *ArgStruct; + unsigned ArgStructAlign : 31; + unsigned HasExtParameterInfos : 1; + + unsigned NumArgs; + + ArgInfo *getArgsBuffer() { return getTrailingObjects(); } + + const ArgInfo *getArgsBuffer() const { return getTrailingObjects(); } + + ExtParameterInfo *getExtParameterInfosBuffer() { + return getTrailingObjects(); + } + + const ExtParameterInfo *getExtParameterInfosBuffer() const { + return getTrailingObjects(); + } + + CIRGenFunctionInfo() : Required(RequiredArgs::All) {} + +public: + static CIRGenFunctionInfo *create(unsigned cirCC, bool instanceMethod, + bool chainCall, + const clang::FunctionType::ExtInfo &extInfo, + llvm::ArrayRef paramInfos, + clang::CanQualType resultType, + llvm::ArrayRef argTypes, + RequiredArgs required); + void operator delete(void *p) { ::operator delete(p); } + + // Friending class TrailingObjects is apparantly not good enough for MSVC, so + // these have to be public. + friend class TrailingObjects; + size_t numTrailingObjects(OverloadToken) const { + return NumArgs + 1; + } + size_t numTrailingObjects(OverloadToken) const { + return (HasExtParameterInfos ? NumArgs : 0); + } + + using const_arg_iterator = const ArgInfo *; + using arg_iterator = ArgInfo *; + + static void Profile(llvm::FoldingSetNodeID &ID, bool InstanceMethod, + bool ChainCall, const clang::FunctionType::ExtInfo &info, + llvm::ArrayRef paramInfos, + RequiredArgs required, clang::CanQualType resultType, + llvm::ArrayRef argTypes) { + ID.AddInteger(info.getCC()); + ID.AddBoolean(InstanceMethod); + ID.AddBoolean(info.getNoReturn()); + ID.AddBoolean(info.getProducesResult()); + ID.AddBoolean(info.getNoCallerSavedRegs()); + ID.AddBoolean(info.getHasRegParm()); + ID.AddBoolean(info.getRegParm()); + ID.AddBoolean(info.getNoCfCheck()); + ID.AddBoolean(info.getCmseNSCall()); + ID.AddBoolean(required.getOpaqueData()); + ID.AddBoolean(!paramInfos.empty()); + if (!paramInfos.empty()) { + for (auto paramInfo : paramInfos) + ID.AddInteger(paramInfo.getOpaqueValue()); + } + resultType.Profile(ID); + for (auto i : argTypes) + i.Profile(ID); + } + + /// getASTCallingConvention() - Return the AST-specified calling convention + clang::CallingConv getASTCallingConvention() const { + return clang::CallingConv(ASTCallingConvention); + } + + void Profile(llvm::FoldingSetNodeID &ID) { + ID.AddInteger(getASTCallingConvention()); + ID.AddBoolean(InstanceMethod); + ID.AddBoolean(ChainCall); + ID.AddBoolean(NoReturn); + ID.AddBoolean(ReturnsRetained); + ID.AddBoolean(NoCallerSavedRegs); + ID.AddBoolean(HasRegParm); + ID.AddBoolean(RegParm); + ID.AddBoolean(NoCfCheck); + ID.AddBoolean(CmseNSCall); + ID.AddInteger(Required.getOpaqueData()); + ID.AddBoolean(HasExtParameterInfos); + if (HasExtParameterInfos) { + for (auto paramInfo : getExtParameterInfos()) + ID.AddInteger(paramInfo.getOpaqueValue()); + } + getReturnType().Profile(ID); + for (const auto &I : arguments()) + I.type.Profile(ID); + } + + llvm::MutableArrayRef arguments() { + return llvm::MutableArrayRef(arg_begin(), NumArgs); + } + llvm::ArrayRef arguments() const { + return llvm::ArrayRef(arg_begin(), NumArgs); + } + + const_arg_iterator arg_begin() const { return getArgsBuffer() + 1; } + const_arg_iterator arg_end() const { return getArgsBuffer() + 1 + NumArgs; } + arg_iterator arg_begin() { return getArgsBuffer() + 1; } + arg_iterator arg_end() { return getArgsBuffer() + 1 + NumArgs; } + + unsigned arg_size() const { return NumArgs; } + + llvm::ArrayRef getExtParameterInfos() const { + if (!HasExtParameterInfos) + return {}; + return llvm::ArrayRef(getExtParameterInfosBuffer(), NumArgs); + } + ExtParameterInfo getExtParameterInfo(unsigned argIndex) const { + assert(argIndex <= NumArgs); + if (!HasExtParameterInfos) + return ExtParameterInfo(); + return getExtParameterInfos()[argIndex]; + } + + /// getCallingConvention - REturn the user specified calling convention, which + /// has been translated into a CIR CC. + unsigned getCallingConvention() const { return CallingConvention; } + + clang::CanQualType getReturnType() const { return getArgsBuffer()[0].type; } + + ABIArgInfo &getReturnInfo() { return getArgsBuffer()[0].info; } + const ABIArgInfo &getReturnInfo() const { return getArgsBuffer()[0].info; } + + bool isChainCall() const { return ChainCall; } + + bool isVariadic() const { return Required.allowsOptionalArgs(); } + RequiredArgs getRequiredArgs() const { return Required; } + unsigned getNumRequiredArgs() const { + return isVariadic() ? getRequiredArgs().getNumRequiredArgs() : arg_size(); + } + + mlir::cir::StructType *getArgStruct() const { return ArgStruct; } + + /// Return true if this function uses inalloca arguments. + bool usesInAlloca() const { return ArgStruct; } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp new file mode 100644 index 000000000000..1d3ec6d3cc79 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -0,0 +1,1992 @@ +//===----- CIRGenItaniumCXXABI.cpp - Emit CIR from ASTs for a Module ------===// +// +// 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 provides C++ code generation targeting the Itanium C++ ABI. The class +// in this file generates structures that follow the Itanium C++ ABI, which is +// documented at: +// https://itanium-cxx-abi.github.io/cxx-abi/abi.html +// https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html +// +// It also supports the closely-related ARM ABI, documented at: +// https://developer.arm.com/documentation/ihi0041/g/ +// +//===----------------------------------------------------------------------===// + +#include "CIRGenCXXABI.h" +#include "CIRGenCleanup.h" +#include "CIRGenFunctionInfo.h" +#include "ConstantInitBuilder.h" + +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Mangle.h" +#include "clang/AST/VTableBuilder.h" +#include "clang/Basic/Linkage.h" +#include "clang/Basic/TargetInfo.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace cir; +using namespace clang; + +namespace { +class CIRGenItaniumCXXABI : public cir::CIRGenCXXABI { + /// All the vtables which have been defined. + llvm::DenseMap VTables; + +protected: + bool UseARMMethodPtrABI; + bool UseARMGuardVarABI; + bool Use32BitVTableOffsetABI; + + ItaniumMangleContext &getMangleContext() { + return cast(cir::CIRGenCXXABI::getMangleContext()); + } + + bool isVTableHidden(const CXXRecordDecl *RD) const { + const auto &VtableLayout = + CGM.getItaniumVTableContext().getVTableLayout(RD); + + for (const auto &VtableComponent : VtableLayout.vtable_components()) { + if (VtableComponent.isRTTIKind()) { + const CXXRecordDecl *RTTIDecl = VtableComponent.getRTTIDecl(); + if (RTTIDecl->getVisibility() == Visibility::HiddenVisibility) + return true; + } else if (VtableComponent.isUsedFunctionPointerKind()) { + const CXXMethodDecl *Method = VtableComponent.getFunctionDecl(); + if (Method->getVisibility() == Visibility::HiddenVisibility && + !Method->isDefined()) + return true; + } + } + return false; + } + + bool hasAnyUnusedVirtualInlineFunction(const CXXRecordDecl *RD) const { + const auto &VtableLayout = + CGM.getItaniumVTableContext().getVTableLayout(RD); + + for (const auto &VtableComponent : VtableLayout.vtable_components()) { + // Skip empty slot. + if (!VtableComponent.isUsedFunctionPointerKind()) + continue; + + const CXXMethodDecl *Method = VtableComponent.getFunctionDecl(); + if (!Method->getCanonicalDecl()->isInlined()) + continue; + + StringRef Name = CGM.getMangledName(VtableComponent.getGlobalDecl()); + auto *op = CGM.getGlobalValue(Name); + if (auto globalOp = dyn_cast_or_null(op)) + llvm_unreachable("NYI"); + + if (auto funcOp = dyn_cast_or_null(op)) { + // This checks if virtual inline function has already been emitted. + // Note that it is possible that this inline function would be emitted + // after trying to emit vtable speculatively. Because of this we do + // an extra pass after emitting all deferred vtables to find and emit + // these vtables opportunistically. + if (!funcOp || funcOp.isDeclaration()) + return true; + } + } + return false; + } + +public: + CIRGenItaniumCXXABI(CIRGenModule &CGM, bool UseARMMethodPtrABI = false, + bool UseARMGuardVarABI = false) + : CIRGenCXXABI(CGM), UseARMMethodPtrABI{UseARMMethodPtrABI}, + UseARMGuardVarABI{UseARMGuardVarABI}, Use32BitVTableOffsetABI{false} { + assert(!UseARMMethodPtrABI && "NYI"); + assert(!UseARMGuardVarABI && "NYI"); + } + AddedStructorArgs getImplicitConstructorArgs(CIRGenFunction &CGF, + const CXXConstructorDecl *D, + CXXCtorType Type, + bool ForVirtualBase, + bool Delegating) override; + + bool NeedsVTTParameter(GlobalDecl GD) override; + + RecordArgABI getRecordArgABI(const clang::CXXRecordDecl *RD) const override { + // If C++ prohibits us from making a copy, pass by address. + if (!RD->canPassInRegisters()) + return RecordArgABI::Indirect; + else + return RecordArgABI::Default; + } + + bool classifyReturnType(CIRGenFunctionInfo &FI) const override; + + AddedStructorArgCounts + buildStructorSignature(GlobalDecl GD, + llvm::SmallVectorImpl &ArgTys) override; + + bool isThisCompleteObject(GlobalDecl GD) const override { + // The Itanium ABI has separate complete-object vs. base-object variants of + // both constructors and destructors. + if (isa(GD.getDecl())) { + llvm_unreachable("NYI"); + } + if (isa(GD.getDecl())) { + switch (GD.getCtorType()) { + case Ctor_Complete: + return true; + + case Ctor_Base: + return false; + + case Ctor_CopyingClosure: + case Ctor_DefaultClosure: + llvm_unreachable("closure ctors in Itanium ABI?"); + + case Ctor_Comdat: + llvm_unreachable("emitting ctor comdat as function?"); + } + llvm_unreachable("bad dtor kind"); + } + + // No other kinds. + return false; + } + + void buildInstanceFunctionProlog(CIRGenFunction &CGF) override; + + void addImplicitStructorParams(CIRGenFunction &CGF, QualType &ResTy, + FunctionArgList &Params) override; + + mlir::Value getCXXDestructorImplicitParam(CIRGenFunction &CGF, + const CXXDestructorDecl *DD, + CXXDtorType Type, + bool ForVirtualBase, + bool Delegating) override; + void buildCXXConstructors(const clang::CXXConstructorDecl *D) override; + void buildCXXDestructors(const clang::CXXDestructorDecl *D) override; + void buildCXXStructor(clang::GlobalDecl GD) override; + void buildDestructorCall(CIRGenFunction &CGF, const CXXDestructorDecl *DD, + CXXDtorType Type, bool ForVirtualBase, + bool Delegating, Address This, + QualType ThisTy) override; + virtual void buildRethrow(CIRGenFunction &CGF, bool isNoReturn) override; + virtual void buildThrow(CIRGenFunction &CGF, const CXXThrowExpr *E) override; + CatchTypeInfo + getAddrOfCXXCatchHandlerType(mlir::Location loc, QualType Ty, + QualType CatchHandlerType) override { + auto rtti = + dyn_cast(getAddrOfRTTIDescriptor(loc, Ty)); + assert(rtti && "expected GlobalViewAttr"); + return CatchTypeInfo{rtti, 0}; + } + + bool canSpeculativelyEmitVTable(const CXXRecordDecl *RD) const override; + mlir::cir::GlobalOp getAddrOfVTable(const CXXRecordDecl *RD, + CharUnits VPtrOffset) override; + CIRGenCallee getVirtualFunctionPointer(CIRGenFunction &CGF, GlobalDecl GD, + Address This, mlir::Type Ty, + SourceLocation Loc) override; + mlir::Value getVTableAddressPoint(BaseSubobject Base, + const CXXRecordDecl *VTableClass) override; + bool isVirtualOffsetNeededForVTableField(CIRGenFunction &CGF, + CIRGenFunction::VPtr Vptr) override; + bool canSpeculativelyEmitVTableAsBaseClass(const CXXRecordDecl *RD) const; + mlir::Value getVTableAddressPointInStructor( + CIRGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base, + const CXXRecordDecl *NearestVBase) override; + void emitVTableDefinitions(CIRGenVTables &CGVT, + const CXXRecordDecl *RD) override; + void emitVirtualInheritanceTables(const CXXRecordDecl *RD) override; + mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc, + QualType Ty) override; + bool useThunkForDtorVariant(const CXXDestructorDecl *Dtor, + CXXDtorType DT) const override { + // Itanium does not emit any destructor variant as an inline thunk. + // Delegating may occur as an optimization, but all variants are either + // emitted with external linkage or as linkonce if they are inline and used. + return false; + } + + /// TODO(cir): seems like could be shared between LLVM IR and CIR codegen. + bool mayNeedDestruction(const VarDecl *VD) const { + if (VD->needsDestruction(getContext())) + return true; + + // If the variable has an incomplete class type (or array thereof), it + // might need destruction. + const Type *T = VD->getType()->getBaseElementTypeUnsafe(); + if (T->getAs() && T->isIncompleteType()) + return true; + + return false; + } + + /// Determine whether we will definitely emit this variable with a constant + /// initializer, either because the language semantics demand it or because + /// we know that the initializer is a constant. + /// For weak definitions, any initializer available in the current translation + /// is not necessarily reflective of the initializer used; such initializers + /// are ignored unless if InspectInitForWeakDef is true. + /// TODO(cir): seems like could be shared between LLVM IR and CIR codegen. + bool + isEmittedWithConstantInitializer(const VarDecl *VD, + bool InspectInitForWeakDef = false) const { + VD = VD->getMostRecentDecl(); + if (VD->hasAttr()) + return true; + + // All later checks examine the initializer specified on the variable. If + // the variable is weak, such examination would not be correct. + if (!InspectInitForWeakDef && + (VD->isWeak() || VD->hasAttr())) + return false; + + const VarDecl *InitDecl = VD->getInitializingDeclaration(); + if (!InitDecl) + return false; + + // If there's no initializer to run, this is constant initialization. + if (!InitDecl->hasInit()) + return true; + + // If we have the only definition, we don't need a thread wrapper if we + // will emit the value as a constant. + if (isUniqueGVALinkage(getContext().GetGVALinkageForVariable(VD))) + return !mayNeedDestruction(VD) && InitDecl->evaluateValue(); + + // Otherwise, we need a thread wrapper unless we know that every + // translation unit will emit the value as a constant. We rely on the + // variable being constant-initialized in every translation unit if it's + // constant-initialized in any translation unit, which isn't actually + // guaranteed by the standard but is necessary for sanity. + return InitDecl->hasConstantInitialization(); + } + + // TODO(cir): seems like could be shared between LLVM IR and CIR codegen. + bool usesThreadWrapperFunction(const VarDecl *VD) const override { + return !isEmittedWithConstantInitializer(VD) || mayNeedDestruction(VD); + } + + bool doStructorsInitializeVPtrs(const CXXRecordDecl *VTableClass) override { + return true; + } + + size_t getSrcArgforCopyCtor(const CXXConstructorDecl *, + FunctionArgList &Args) const override { + assert(!Args.empty() && "expected the arglist to not be empty!"); + return Args.size() - 1; + } + + /**************************** RTTI Uniqueness ******************************/ +protected: + /// Returns true if the ABI requires RTTI type_info objects to be unique + /// across a program. + virtual bool shouldRTTIBeUnique() const { return true; } + +public: + /// What sort of unique-RTTI behavior should we use? + enum RTTIUniquenessKind { + /// We are guaranteeing, or need to guarantee, that the RTTI string + /// is unique. + RUK_Unique, + + /// We are not guaranteeing uniqueness for the RTTI string, so we + /// can demote to hidden visibility but must use string comparisons. + RUK_NonUniqueHidden, + + /// We are not guaranteeing uniqueness for the RTTI string, so we + /// have to use string comparisons, but we also have to emit it with + /// non-hidden visibility. + RUK_NonUniqueVisible + }; + + /// Return the required visibility status for the given type and linkage in + /// the current ABI. + RTTIUniquenessKind + classifyRTTIUniqueness(QualType CanTy, + mlir::cir::GlobalLinkageKind Linkage) const; + friend class CIRGenItaniumRTTIBuilder; +}; +} // namespace + +CIRGenCXXABI::AddedStructorArgs CIRGenItaniumCXXABI::getImplicitConstructorArgs( + CIRGenFunction &CGF, const CXXConstructorDecl *D, CXXCtorType Type, + bool ForVirtualBase, bool Delegating) { + assert(!NeedsVTTParameter(GlobalDecl(D, Type)) && "VTT NYI"); + + return {}; +} + +/// Return whether the given global decl needs a VTT parameter, which it does if +/// it's a base constructor or destructor with virtual bases. +bool CIRGenItaniumCXXABI::NeedsVTTParameter(GlobalDecl GD) { + auto *MD = cast(GD.getDecl()); + + // We don't have any virtual bases, just return early. + if (!MD->getParent()->getNumVBases()) + return false; + + // Check if we have a base constructor. + if (isa(MD) && GD.getCtorType() == Ctor_Base) + return true; + + // Check if we have a base destructor. + if (isa(MD) && GD.getDtorType() == Dtor_Base) + llvm_unreachable("NYI"); + + return false; +} + +CIRGenCXXABI *cir::CreateCIRGenItaniumCXXABI(CIRGenModule &CGM) { + switch (CGM.getASTContext().getCXXABIKind()) { + case TargetCXXABI::GenericItanium: + assert(CGM.getASTContext().getTargetInfo().getTriple().getArch() != + llvm::Triple::le32 && + "le32 NYI"); + LLVM_FALLTHROUGH; + case TargetCXXABI::GenericAArch64: + case TargetCXXABI::AppleARM64: + // TODO: this isn't quite right, clang uses AppleARM64CXXABI which inherits + // from ARMCXXABI. We'll have to follow suit. + return new CIRGenItaniumCXXABI(CGM); + + default: + llvm_unreachable("bad or NYI ABI kind"); + } +} + +bool CIRGenItaniumCXXABI::classifyReturnType(CIRGenFunctionInfo &FI) const { + auto *RD = FI.getReturnType()->getAsCXXRecordDecl(); + assert(!RD && "RecordDecl return types NYI"); + return false; +} + +CIRGenCXXABI::AddedStructorArgCounts +CIRGenItaniumCXXABI::buildStructorSignature( + GlobalDecl GD, llvm::SmallVectorImpl &ArgTys) { + auto &Context = getContext(); + + // All parameters are already in place except VTT, which goes after 'this'. + // These are clang types, so we don't need to worry about sret yet. + + // Check if we need to add a VTT parameter (which has type void **). + if ((isa(GD.getDecl()) ? GD.getCtorType() == Ctor_Base + : GD.getDtorType() == Dtor_Base) && + cast(GD.getDecl())->getParent()->getNumVBases() != 0) { + llvm_unreachable("NYI"); + (void)Context; + } + + return AddedStructorArgCounts{}; +} + +// Find out how to cirgen the complete destructor and constructor +namespace { +enum class StructorCIRGen { Emit, RAUW, Alias, COMDAT }; +} + +static StructorCIRGen getCIRGenToUse(CIRGenModule &CGM, + const CXXMethodDecl *MD) { + if (!CGM.getCodeGenOpts().CXXCtorDtorAliases) + return StructorCIRGen::Emit; + + // The complete and base structors are not equivalent if there are any virtual + // bases, so emit separate functions. + if (MD->getParent()->getNumVBases()) + return StructorCIRGen::Emit; + + GlobalDecl AliasDecl; + if (const auto *DD = dyn_cast(MD)) { + AliasDecl = GlobalDecl(DD, Dtor_Complete); + } else { + const auto *CD = cast(MD); + AliasDecl = GlobalDecl(CD, Ctor_Complete); + } + auto Linkage = CGM.getFunctionLinkage(AliasDecl); + (void)Linkage; + + if (mlir::cir::isDiscardableIfUnused(Linkage)) + return StructorCIRGen::RAUW; + + // FIXME: Should we allow available_externally aliases? + if (!mlir::cir::isValidLinkage(Linkage)) + return StructorCIRGen::RAUW; + + if (mlir::cir::isWeakForLinker(Linkage)) { + // Only ELF and wasm support COMDATs with arbitrary names (C5/D5). + if (CGM.getTarget().getTriple().isOSBinFormatELF() || + CGM.getTarget().getTriple().isOSBinFormatWasm()) + return StructorCIRGen::COMDAT; + return StructorCIRGen::Emit; + } + + return StructorCIRGen::Alias; +} + +static void emitConstructorDestructorAlias(CIRGenModule &CGM, + GlobalDecl AliasDecl, + GlobalDecl TargetDecl) { + auto Linkage = CGM.getFunctionLinkage(AliasDecl); + + // Does this function alias already exists? + StringRef MangledName = CGM.getMangledName(AliasDecl); + auto Entry = + dyn_cast_or_null(CGM.getGlobalValue(MangledName)); + if (Entry && !Entry.isDeclaration()) + return; + + // Retrieve aliasee info. + auto Aliasee = + dyn_cast_or_null(CGM.GetAddrOfGlobal(TargetDecl)); + assert(Aliasee && "expected cir.func"); + auto *AliasFD = dyn_cast(AliasDecl.getDecl()); + assert(AliasFD && "expected FunctionDecl"); + + // Populate actual alias. + auto Alias = + CGM.createCIRFunction(CGM.getLoc(AliasDecl.getDecl()->getSourceRange()), + MangledName, Aliasee.getFunctionType(), AliasFD); + Alias.setAliasee(Aliasee.getName()); + Alias.setLinkage(Linkage); + mlir::SymbolTable::setSymbolVisibility( + Alias, CGM.getMLIRVisibilityFromCIRLinkage(Linkage)); + + // Alias constructors and destructors are always unnamed_addr. + assert(!UnimplementedFeature::unnamedAddr()); + + // Switch any previous uses to the alias. + if (Entry) { + llvm_unreachable("NYI"); + } else { + // Name already set by createCIRFunction + } + + // Finally, set up the alias with its proper name and attributes. + CGM.setCommonAttributes(AliasDecl, Alias); +} + +void CIRGenItaniumCXXABI::buildCXXStructor(GlobalDecl GD) { + auto *MD = cast(GD.getDecl()); + auto *CD = dyn_cast(MD); + const CXXDestructorDecl *DD = CD ? nullptr : cast(MD); + + StructorCIRGen CIRGenType = getCIRGenToUse(CGM, MD); + + if (CD ? GD.getCtorType() == Ctor_Complete + : GD.getDtorType() == Dtor_Complete) { + GlobalDecl BaseDecl; + if (CD) + BaseDecl = GD.getWithCtorType(Ctor_Base); + else + BaseDecl = GD.getWithDtorType(Dtor_Base); + + if (CIRGenType == StructorCIRGen::Alias || + CIRGenType == StructorCIRGen::COMDAT) { + emitConstructorDestructorAlias(CGM, GD, BaseDecl); + return; + } + + if (CIRGenType == StructorCIRGen::RAUW) { + StringRef MangledName = CGM.getMangledName(GD); + auto *Aliasee = CGM.GetAddrOfGlobal(BaseDecl); + CGM.addReplacement(MangledName, Aliasee); + return; + } + } + + // The base destructor is equivalent to the base destructor of its base class + // if there is exactly one non-virtual base class with a non-trivial + // destructor, there are no fields with a non-trivial destructor, and the body + // of the destructor is trivial. + if (DD && GD.getDtorType() == Dtor_Base && + CIRGenType != StructorCIRGen::COMDAT) + return; + + // FIXME: The deleting destructor is equivalent to the selected operator + // delete if: + // * either the delete is a destroying operator delete or the destructor + // would be trivial if it weren't virtual. + // * the conversion from the 'this' parameter to the first parameter of the + // destructor is equivalent to a bitcast, + // * the destructor does not have an implicit "this" return, and + // * the operator delete has the same calling convention and CIR function + // type as the destructor. + // In such cases we should try to emit the deleting dtor as an alias to the + // selected 'operator delete'. + + auto Fn = CGM.codegenCXXStructor(GD); + + if (CIRGenType == StructorCIRGen::COMDAT) { + llvm_unreachable("NYI"); + } else { + CGM.maybeSetTrivialComdat(*MD, Fn); + } +} + +void CIRGenItaniumCXXABI::addImplicitStructorParams(CIRGenFunction &CGF, + QualType &ResTY, + FunctionArgList &Params) { + const auto *MD = cast(CGF.CurGD.getDecl()); + assert(isa(MD) || isa(MD)); + + // Check if we need a VTT parameter as well. + if (NeedsVTTParameter(CGF.CurGD)) { + llvm_unreachable("NYI"); + } +} + +mlir::Value CIRGenCXXABI::loadIncomingCXXThis(CIRGenFunction &CGF) { + return CGF.createLoad(getThisDecl(CGF), "this"); +} + +void CIRGenCXXABI::setCXXABIThisValue(CIRGenFunction &CGF, + mlir::Value ThisPtr) { + /// Initialize the 'this' slot. + assert(getThisDecl(CGF) && "no 'this' variable for function"); + CGF.CXXABIThisValue = ThisPtr; +} + +void CIRGenItaniumCXXABI::buildInstanceFunctionProlog(CIRGenFunction &CGF) { + // Naked functions have no prolog. + if (CGF.CurFuncDecl && CGF.CurFuncDecl->hasAttr()) + llvm_unreachable("NYI"); + + /// Initialize the 'this' slot. In the Itanium C++ ABI, no prologue + /// adjustments are required, because they are all handled by thunks. + setCXXABIThisValue(CGF, loadIncomingCXXThis(CGF)); + + /// Initialize the 'vtt' slot if needed. + if (getStructorImplicitParamDecl(CGF)) { + llvm_unreachable("NYI"); + } + + /// If this is a function that the ABI specifies returns 'this', initialize + /// the return slot to this' at the start of the function. + /// + /// Unlike the setting of return types, this is done within the ABI + /// implementation instead of by clients of CIRGenCXXBI because: + /// 1) getThisValue is currently protected + /// 2) in theory, an ABI could implement 'this' returns some other way; + /// HasThisReturn only specifies a contract, not the implementation + if (HasThisReturn(CGF.CurGD)) + llvm_unreachable("NYI"); +} + +void CIRGenItaniumCXXABI::buildCXXConstructors(const CXXConstructorDecl *D) { + // Just make sure we're in sync with TargetCXXABI. + assert(CGM.getTarget().getCXXABI().hasConstructorVariants()); + + // The constructor used for constructing this as a base class; + // ignores virtual bases. + CGM.buildGlobal(GlobalDecl(D, Ctor_Base)); + + // The constructor used for constructing this as a complete class; + // constructs the virtual bases, then calls the base constructor. + if (!D->getParent()->isAbstract()) { + // We don't need to emit the complete ctro if the class is abstract. + CGM.buildGlobal(GlobalDecl(D, Ctor_Complete)); + } +} + +void CIRGenItaniumCXXABI::buildCXXDestructors(const CXXDestructorDecl *D) { + // The destructor used for destructing this as a base class; ignores + // virtual bases. + CGM.buildGlobal(GlobalDecl(D, Dtor_Base)); + + // The destructor used for destructing this as a most-derived class; + // call the base destructor and then destructs any virtual bases. + CGM.buildGlobal(GlobalDecl(D, Dtor_Complete)); + + // The destructor in a virtual table is always a 'deleting' + // destructor, which calls the complete destructor and then uses the + // appropriate operator delete. + if (D->isVirtual()) + CGM.buildGlobal(GlobalDecl(D, Dtor_Deleting)); +} + +mlir::cir::GlobalOp +CIRGenItaniumCXXABI::getAddrOfVTable(const CXXRecordDecl *RD, + CharUnits VPtrOffset) { + assert(VPtrOffset.isZero() && "Itanium ABI only supports zero vptr offsets"); + mlir::cir::GlobalOp &vtable = VTables[RD]; + if (vtable) + return vtable; + + // Queue up this vtable for possible deferred emission. + CGM.addDeferredVTable(RD); + + SmallString<256> Name; + llvm::raw_svector_ostream Out(Name); + getMangleContext().mangleCXXVTable(RD, Out); + + const VTableLayout &VTLayout = + CGM.getItaniumVTableContext().getVTableLayout(RD); + auto VTableType = CGM.getVTables().getVTableType(VTLayout); + + // Use pointer alignment for the vtable. Otherwise we would align them based + // on the size of the initializer which doesn't make sense as only single + // values are read. + unsigned PAlign = CGM.getItaniumVTableContext().isRelativeLayout() + ? 32 + : CGM.getTarget().getPointerAlign(LangAS::Default); + + vtable = CGM.createOrReplaceCXXRuntimeVariable( + CGM.getLoc(RD->getSourceRange()), Name, VTableType, + mlir::cir::GlobalLinkageKind::ExternalLinkage, + getContext().toCharUnitsFromBits(PAlign)); + // LLVM codegen handles unnamedAddr + assert(!UnimplementedFeature::unnamedAddr()); + + // In MS C++ if you have a class with virtual functions in which you are using + // selective member import/export, then all virtual functions must be exported + // unless they are inline, otherwise a link error will result. To match this + // behavior, for such classes, we dllimport the vtable if it is defined + // externally and all the non-inline virtual methods are marked dllimport, and + // we dllexport the vtable if it is defined in this TU and all the non-inline + // virtual methods are marked dllexport. + if (CGM.getTarget().hasPS4DLLImportExport()) + llvm_unreachable("NYI"); + + CGM.setGVProperties(vtable, RD); + return vtable; +} + +CIRGenCallee CIRGenItaniumCXXABI::getVirtualFunctionPointer( + CIRGenFunction &CGF, GlobalDecl GD, Address This, mlir::Type Ty, + SourceLocation Loc) { + auto loc = CGF.getLoc(Loc); + auto TyPtr = CGF.getBuilder().getPointerTo(Ty); + auto *MethodDecl = cast(GD.getDecl()); + auto VTable = CGF.getVTablePtr( + Loc, This, CGF.getBuilder().getPointerTo(TyPtr), MethodDecl->getParent()); + + uint64_t VTableIndex = CGM.getItaniumVTableContext().getMethodVTableIndex(GD); + mlir::Value VFunc{}; + if (CGF.shouldEmitVTableTypeCheckedLoad(MethodDecl->getParent())) { + llvm_unreachable("NYI"); + } else { + CGF.buildTypeMetadataCodeForVCall(MethodDecl->getParent(), VTable, Loc); + + mlir::Value VFuncLoad; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + llvm_unreachable("NYI"); + } else { + VTable = CGF.getBuilder().createBitcast( + loc, VTable, CGF.getBuilder().getPointerTo(TyPtr)); + auto VTableSlotPtr = + CGF.getBuilder().create( + loc, CGF.getBuilder().getPointerTo(TyPtr), + ::mlir::FlatSymbolRefAttr{}, VTable, + /*vtable_index=*/0, VTableIndex); + VFuncLoad = CGF.getBuilder().createAlignedLoad(loc, TyPtr, VTableSlotPtr, + CGF.getPointerAlign()); + } + + // Add !invariant.load md to virtual function load to indicate that + // function didn't change inside vtable. + // It's safe to add it without -fstrict-vtable-pointers, but it would not + // help in devirtualization because it will only matter if we will have 2 + // the same virtual function loads from the same vtable load, which won't + // happen without enabled devirtualization with -fstrict-vtable-pointers. + if (CGM.getCodeGenOpts().OptimizationLevel > 0 && + CGM.getCodeGenOpts().StrictVTablePointers) { + llvm_unreachable("NYI"); + } + VFunc = VFuncLoad; + } + + CIRGenCallee Callee(GD, VFunc.getDefiningOp()); + return Callee; +} + +mlir::Value +CIRGenItaniumCXXABI::getVTableAddressPoint(BaseSubobject Base, + const CXXRecordDecl *VTableClass) { + auto vtable = getAddrOfVTable(VTableClass, CharUnits()); + + // Find the appropriate vtable within the vtable group, and the address point + // within that vtable. + VTableLayout::AddressPointLocation AddressPoint = + CGM.getItaniumVTableContext() + .getVTableLayout(VTableClass) + .getAddressPoint(Base); + + auto &builder = CGM.getBuilder(); + auto vtablePtrTy = builder.getVirtualFnPtrType(/*isVarArg=*/false); + + return builder.create( + CGM.getLoc(VTableClass->getSourceRange()), vtablePtrTy, + mlir::FlatSymbolRefAttr::get(vtable.getSymNameAttr()), mlir::Value{}, + AddressPoint.VTableIndex, AddressPoint.AddressPointIndex); +} + +mlir::Value CIRGenItaniumCXXABI::getVTableAddressPointInStructor( + CIRGenFunction &CGF, const CXXRecordDecl *VTableClass, BaseSubobject Base, + const CXXRecordDecl *NearestVBase) { + + if ((Base.getBase()->getNumVBases() || NearestVBase != nullptr) && + NeedsVTTParameter(CGF.CurGD)) { + llvm_unreachable("NYI"); + } + return getVTableAddressPoint(Base, VTableClass); +} + +bool CIRGenItaniumCXXABI::isVirtualOffsetNeededForVTableField( + CIRGenFunction &CGF, CIRGenFunction::VPtr Vptr) { + if (Vptr.NearestVBase == nullptr) + return false; + return NeedsVTTParameter(CGF.CurGD); +} + +bool CIRGenItaniumCXXABI::canSpeculativelyEmitVTableAsBaseClass( + const CXXRecordDecl *RD) const { + // We don't emit available_externally vtables if we are in -fapple-kext mode + // because kext mode does not permit devirtualization. + if (CGM.getLangOpts().AppleKext) + return false; + + // If the vtable is hidden then it is not safe to emit an available_externally + // copy of vtable. + if (isVTableHidden(RD)) + return false; + + if (CGM.getCodeGenOpts().ForceEmitVTables) + return true; + + // If we don't have any not emitted inline virtual function then we are safe + // to emit an available_externally copy of vtable. + // FIXME we can still emit a copy of the vtable if we + // can emit definition of the inline functions. + if (hasAnyUnusedVirtualInlineFunction(RD)) + return false; + + // For a class with virtual bases, we must also be able to speculatively + // emit the VTT, because CodeGen doesn't have separate notions of "can emit + // the vtable" and "can emit the VTT". For a base subobject, this means we + // need to be able to emit non-virtual base vtables. + if (RD->getNumVBases()) { + for (const auto &B : RD->bases()) { + auto *BRD = B.getType()->getAsCXXRecordDecl(); + assert(BRD && "no class for base specifier"); + if (B.isVirtual() || !BRD->isDynamicClass()) + continue; + if (!canSpeculativelyEmitVTableAsBaseClass(BRD)) + return false; + } + } + + return true; +} + +bool CIRGenItaniumCXXABI::canSpeculativelyEmitVTable( + const CXXRecordDecl *RD) const { + if (!canSpeculativelyEmitVTableAsBaseClass(RD)) + return false; + + // For a complete-object vtable (or more specifically, for the VTT), we need + // to be able to speculatively emit the vtables of all dynamic virtual bases. + for (const auto &B : RD->vbases()) { + auto *BRD = B.getType()->getAsCXXRecordDecl(); + assert(BRD && "no class for base specifier"); + if (!BRD->isDynamicClass()) + continue; + if (!canSpeculativelyEmitVTableAsBaseClass(BRD)) + return false; + } + + return true; +} + +namespace { +class CIRGenItaniumRTTIBuilder { + CIRGenModule &CGM; // Per-module state. + const CIRGenItaniumCXXABI &CXXABI; // Per-module state. + + /// The fields of the RTTI descriptor currently being built. + SmallVector Fields; + + // Returns the mangled type name of the given type. + mlir::cir::GlobalOp GetAddrOfTypeName(mlir::Location loc, QualType Ty, + mlir::cir::GlobalLinkageKind Linkage); + + // /// Returns the constant for the RTTI + // /// descriptor of the given type. + mlir::Attribute GetAddrOfExternalRTTIDescriptor(mlir::Location loc, + QualType Ty); + + /// Build the vtable pointer for the given type. + void BuildVTablePointer(mlir::Location loc, const Type *Ty); + + /// Build an abi::__si_class_type_info, used for single inheritance, according + /// to the Itanium C++ ABI, 2.9.5p6b. + void BuildSIClassTypeInfo(mlir::Location loc, const CXXRecordDecl *RD); + + /// Build an abi::__vmi_class_type_info, used for + /// classes with bases that do not satisfy the abi::__si_class_type_info + /// constraints, according ti the Itanium C++ ABI, 2.9.5p5c. + void BuildVMIClassTypeInfo(mlir::Location loc, const CXXRecordDecl *RD); + + // /// Build an abi::__pointer_type_info struct, used + // /// for pointer types. + // void BuildPointerTypeInfo(QualType PointeeTy); + + // /// Build the appropriate kind of + // /// type_info for an object type. + // void BuildObjCObjectTypeInfo(const ObjCObjectType *Ty); + + // /// Build an + // abi::__pointer_to_member_type_info + // /// struct, used for member pointer types. + // void BuildPointerToMemberTypeInfo(const MemberPointerType *Ty); + +public: + CIRGenItaniumRTTIBuilder(const CIRGenItaniumCXXABI &ABI, CIRGenModule &_CGM) + : CGM(_CGM), CXXABI(ABI) {} + + // Pointer type info flags. + enum { + /// PTI_Const - Type has const qualifier. + PTI_Const = 0x1, + + /// PTI_Volatile - Type has volatile qualifier. + PTI_Volatile = 0x2, + + /// PTI_Restrict - Type has restrict qualifier. + PTI_Restrict = 0x4, + + /// PTI_Incomplete - Type is incomplete. + PTI_Incomplete = 0x8, + + /// PTI_ContainingClassIncomplete - Containing class is incomplete. + /// (in pointer to member). + PTI_ContainingClassIncomplete = 0x10, + + /// PTI_TransactionSafe - Pointee is transaction_safe function (C++ TM TS). + // PTI_TransactionSafe = 0x20, + + /// PTI_Noexcept - Pointee is noexcept function (C++1z). + PTI_Noexcept = 0x40, + }; + + // VMI type info flags. + enum { + /// VMI_NonDiamondRepeat - Class has non-diamond repeated inheritance. + VMI_NonDiamondRepeat = 0x1, + + /// VMI_DiamondShaped - Class is diamond shaped. + VMI_DiamondShaped = 0x2 + }; + + // Base class type info flags. + enum { + /// BCTI_Virtual - Base class is virtual. + BCTI_Virtual = 0x1, + + /// BCTI_Public - Base class is public. + BCTI_Public = 0x2 + }; + + /// Build the RTTI type info struct for the given type, or + /// link to an existing RTTI descriptor if one already exists. + mlir::Attribute BuildTypeInfo(mlir::Location loc, QualType Ty); + + /// Build the RTTI type info struct for the given type. + mlir::Attribute BuildTypeInfo(mlir::Location loc, QualType Ty, + mlir::cir::GlobalLinkageKind Linkage, + mlir::SymbolTable::Visibility Visibility); +}; +} // namespace + +/// Given a builtin type, returns whether the type +/// info for that type is defined in the standard library. +/// TODO(cir): this can unified with LLVM codegen +static bool TypeInfoIsInStandardLibrary(const BuiltinType *Ty) { + // Itanium C++ ABI 2.9.2: + // Basic type information (e.g. for "int", "bool", etc.) will be kept in + // the run-time support library. Specifically, the run-time support + // library should contain type_info objects for the types X, X* and + // X const*, for every X in: void, std::nullptr_t, bool, wchar_t, char, + // unsigned char, signed char, short, unsigned short, int, unsigned int, + // long, unsigned long, long long, unsigned long long, float, double, + // long double, char16_t, char32_t, and the IEEE 754r decimal and + // half-precision floating point types. + // + // GCC also emits RTTI for __int128. + // FIXME: We do not emit RTTI information for decimal types here. + + // Types added here must also be added to EmitFundamentalRTTIDescriptors. + switch (Ty->getKind()) { + case BuiltinType::WasmExternRef: + llvm_unreachable("NYI"); + case BuiltinType::Void: + case BuiltinType::NullPtr: + case BuiltinType::Bool: + case BuiltinType::WChar_S: + case BuiltinType::WChar_U: + case BuiltinType::Char_U: + case BuiltinType::Char_S: + case BuiltinType::UChar: + case BuiltinType::SChar: + case BuiltinType::Short: + case BuiltinType::UShort: + case BuiltinType::Int: + case BuiltinType::UInt: + case BuiltinType::Long: + case BuiltinType::ULong: + case BuiltinType::LongLong: + case BuiltinType::ULongLong: + case BuiltinType::Half: + case BuiltinType::Float: + case BuiltinType::Double: + case BuiltinType::LongDouble: + case BuiltinType::Float16: + case BuiltinType::Float128: + case BuiltinType::Ibm128: + case BuiltinType::Char8: + case BuiltinType::Char16: + case BuiltinType::Char32: + case BuiltinType::Int128: + case BuiltinType::UInt128: + return true; + +#define IMAGE_TYPE(ImgType, Id, SingletonId, Access, Suffix) \ + case BuiltinType::Id: +#include "clang/Basic/OpenCLImageTypes.def" +#define EXT_OPAQUE_TYPE(ExtType, Id, Ext) case BuiltinType::Id: +#include "clang/Basic/OpenCLExtensionTypes.def" + case BuiltinType::OCLSampler: + case BuiltinType::OCLEvent: + case BuiltinType::OCLClkEvent: + case BuiltinType::OCLQueue: + case BuiltinType::OCLReserveID: +#define SVE_TYPE(Name, Id, SingletonId) case BuiltinType::Id: +#include "clang/Basic/AArch64SVEACLETypes.def" +#define PPC_VECTOR_TYPE(Name, Id, Size) case BuiltinType::Id: +#include "clang/Basic/PPCTypes.def" +#define RVV_TYPE(Name, Id, SingletonId) case BuiltinType::Id: +#include "clang/Basic/RISCVVTypes.def" + case BuiltinType::ShortAccum: + case BuiltinType::Accum: + case BuiltinType::LongAccum: + case BuiltinType::UShortAccum: + case BuiltinType::UAccum: + case BuiltinType::ULongAccum: + case BuiltinType::ShortFract: + case BuiltinType::Fract: + case BuiltinType::LongFract: + case BuiltinType::UShortFract: + case BuiltinType::UFract: + case BuiltinType::ULongFract: + case BuiltinType::SatShortAccum: + case BuiltinType::SatAccum: + case BuiltinType::SatLongAccum: + case BuiltinType::SatUShortAccum: + case BuiltinType::SatUAccum: + case BuiltinType::SatULongAccum: + case BuiltinType::SatShortFract: + case BuiltinType::SatFract: + case BuiltinType::SatLongFract: + case BuiltinType::SatUShortFract: + case BuiltinType::SatUFract: + case BuiltinType::SatULongFract: + case BuiltinType::BFloat16: + return false; + + case BuiltinType::Dependent: +#define BUILTIN_TYPE(Id, SingletonId) +#define PLACEHOLDER_TYPE(Id, SingletonId) case BuiltinType::Id: +#include "clang/AST/BuiltinTypes.def" + llvm_unreachable("asking for RRTI for a placeholder type!"); + + case BuiltinType::ObjCId: + case BuiltinType::ObjCClass: + case BuiltinType::ObjCSel: + llvm_unreachable("FIXME: Objective-C types are unsupported!"); + } + + llvm_unreachable("Invalid BuiltinType Kind!"); +} + +static bool TypeInfoIsInStandardLibrary(const PointerType *PointerTy) { + QualType PointeeTy = PointerTy->getPointeeType(); + const BuiltinType *BuiltinTy = dyn_cast(PointeeTy); + if (!BuiltinTy) + return false; + + // Check the qualifiers. + Qualifiers Quals = PointeeTy.getQualifiers(); + Quals.removeConst(); + + if (!Quals.empty()) + return false; + + return TypeInfoIsInStandardLibrary(BuiltinTy); +} + +/// Returns whether the type +/// information for the given type exists in the standard library. +/// TODO(cir): this can unified with LLVM codegen +static bool IsStandardLibraryRTTIDescriptor(QualType Ty) { + // Type info for builtin types is defined in the standard library. + if (const BuiltinType *BuiltinTy = dyn_cast(Ty)) + return TypeInfoIsInStandardLibrary(BuiltinTy); + + // Type info for some pointer types to builtin types is defined in the + // standard library. + if (const PointerType *PointerTy = dyn_cast(Ty)) + return TypeInfoIsInStandardLibrary(PointerTy); + + return false; +} + +/// Returns whether the type information for +/// the given type exists somewhere else, and that we should not emit the type +/// information in this translation unit. Assumes that it is not a +/// standard-library type. +/// TODO(cir): this can unified with LLVM codegen +static bool ShouldUseExternalRTTIDescriptor(CIRGenModule &CGM, QualType Ty) { + ASTContext &Context = CGM.getASTContext(); + + // If RTTI is disabled, assume it might be disabled in the + // translation unit that defines any potential key function, too. + if (!Context.getLangOpts().RTTI) + return false; + + if (const RecordType *RecordTy = dyn_cast(Ty)) { + const CXXRecordDecl *RD = cast(RecordTy->getDecl()); + if (!RD->hasDefinition()) + return false; + + if (!RD->isDynamicClass()) + return false; + + // FIXME: this may need to be reconsidered if the key function + // changes. + // N.B. We must always emit the RTTI data ourselves if there exists a key + // function. + bool IsDLLImport = RD->hasAttr(); + + // Don't import the RTTI but emit it locally. + if (CGM.getTriple().isWindowsGNUEnvironment()) + return false; + + if (CGM.getVTables().isVTableExternal(RD)) { + if (CGM.getTarget().hasPS4DLLImportExport()) + return true; + + return IsDLLImport && !CGM.getTriple().isWindowsItaniumEnvironment() + ? false + : true; + } + if (IsDLLImport) + return true; + } + + return false; +} + +/// Returns whether the given record type is incomplete. +/// TODO(cir): this can unified with LLVM codegen +static bool IsIncompleteClassType(const RecordType *RecordTy) { + return !RecordTy->getDecl()->isCompleteDefinition(); +} + +/// Returns whether the given type contains an +/// incomplete class type. This is true if +/// +/// * The given type is an incomplete class type. +/// * The given type is a pointer type whose pointee type contains an +/// incomplete class type. +/// * The given type is a member pointer type whose class is an incomplete +/// class type. +/// * The given type is a member pointer type whoise pointee type contains an +/// incomplete class type. +/// is an indirect or direct pointer to an incomplete class type. +/// TODO(cir): this can unified with LLVM codegen +static bool ContainsIncompleteClassType(QualType Ty) { + if (const RecordType *RecordTy = dyn_cast(Ty)) { + if (IsIncompleteClassType(RecordTy)) + return true; + } + + if (const PointerType *PointerTy = dyn_cast(Ty)) + return ContainsIncompleteClassType(PointerTy->getPointeeType()); + + if (const MemberPointerType *MemberPointerTy = + dyn_cast(Ty)) { + // Check if the class type is incomplete. + const RecordType *ClassType = cast(MemberPointerTy->getClass()); + if (IsIncompleteClassType(ClassType)) + return true; + + return ContainsIncompleteClassType(MemberPointerTy->getPointeeType()); + } + + return false; +} + +// Return whether the given record decl has a "single, +// public, non-virtual base at offset zero (i.e. the derived class is dynamic +// iff the base is)", according to Itanium C++ ABI, 2.95p6b. +// TODO(cir): this can unified with LLVM codegen +static bool CanUseSingleInheritance(const CXXRecordDecl *RD) { + // Check the number of bases. + if (RD->getNumBases() != 1) + return false; + + // Get the base. + CXXRecordDecl::base_class_const_iterator Base = RD->bases_begin(); + + // Check that the base is not virtual. + if (Base->isVirtual()) + return false; + + // Check that the base is public. + if (Base->getAccessSpecifier() != AS_public) + return false; + + // Check that the class is dynamic iff the base is. + auto *BaseDecl = + cast(Base->getType()->castAs()->getDecl()); + if (!BaseDecl->isEmpty() && + BaseDecl->isDynamicClass() != RD->isDynamicClass()) + return false; + + return true; +} + +/// Return the linkage that the type info and type info name constants +/// should have for the given type. +static mlir::cir::GlobalLinkageKind getTypeInfoLinkage(CIRGenModule &CGM, + QualType Ty) { + // Itanium C++ ABI 2.9.5p7: + // In addition, it and all of the intermediate abi::__pointer_type_info + // structs in the chain down to the abi::__class_type_info for the + // incomplete class type must be prevented from resolving to the + // corresponding type_info structs for the complete class type, possibly + // by making them local static objects. Finally, a dummy class RTTI is + // generated for the incomplete type that will not resolve to the final + // complete class RTTI (because the latter need not exist), possibly by + // making it a local static object. + if (ContainsIncompleteClassType(Ty)) + return mlir::cir::GlobalLinkageKind::InternalLinkage; + + switch (Ty->getLinkage()) { + case NoLinkage: + case InternalLinkage: + case UniqueExternalLinkage: + return mlir::cir::GlobalLinkageKind::InternalLinkage; + + case VisibleNoLinkage: + case ModuleLinkage: + case ExternalLinkage: + // RTTI is not enabled, which means that this type info struct is going + // to be used for exception handling. Give it linkonce_odr linkage. + if (!CGM.getLangOpts().RTTI) + return mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage; + + if (const RecordType *Record = dyn_cast(Ty)) { + const CXXRecordDecl *RD = cast(Record->getDecl()); + if (RD->hasAttr()) + return mlir::cir::GlobalLinkageKind::WeakODRLinkage; + if (CGM.getTriple().isWindowsItaniumEnvironment()) + if (RD->hasAttr() && + ShouldUseExternalRTTIDescriptor(CGM, Ty)) + return mlir::cir::GlobalLinkageKind::ExternalLinkage; + // MinGW always uses LinkOnceODRLinkage for type info. + if (RD->isDynamicClass() && !CGM.getASTContext() + .getTargetInfo() + .getTriple() + .isWindowsGNUEnvironment()) + return CGM.getVTableLinkage(RD); + } + + return mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage; + } + + llvm_unreachable("Invalid linkage!"); +} + +mlir::Attribute CIRGenItaniumRTTIBuilder::BuildTypeInfo(mlir::Location loc, + QualType Ty) { + // We want to operate on the canonical type. + Ty = Ty.getCanonicalType(); + + // Check if we've already emitted an RTTI descriptor for this type. + SmallString<256> Name; + llvm::raw_svector_ostream Out(Name); + CGM.getCXXABI().getMangleContext().mangleCXXRTTI(Ty, Out); + + auto OldGV = dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(CGM.getModule(), Name)); + + if (OldGV && !OldGV.isDeclaration()) { + assert(!OldGV.hasAvailableExternallyLinkage() && + "available_externally typeinfos not yet implemented"); + return CGM.getBuilder().getGlobalViewAttr(CGM.getBuilder().getUInt8PtrTy(), + OldGV); + } + + // Check if there is already an external RTTI descriptor for this type. + if (IsStandardLibraryRTTIDescriptor(Ty) || + ShouldUseExternalRTTIDescriptor(CGM, Ty)) + return GetAddrOfExternalRTTIDescriptor(loc, Ty); + + // Emit the standard library with external linkage. + auto Linkage = getTypeInfoLinkage(CGM, Ty); + + // Give the type_info object and name the formal visibility of the + // type itself. + assert(!UnimplementedFeature::hiddenVisibility()); + assert(!UnimplementedFeature::protectedVisibility()); + mlir::SymbolTable::Visibility symVisibility; + if (mlir::cir::isLocalLinkage(Linkage)) + // If the linkage is local, only default visibility makes sense. + symVisibility = mlir::SymbolTable::Visibility::Public; + else if (CXXABI.classifyRTTIUniqueness(Ty, Linkage) == + CIRGenItaniumCXXABI::RUK_NonUniqueHidden) + llvm_unreachable("NYI"); + else + symVisibility = CIRGenModule::getCIRVisibility(Ty->getVisibility()); + + assert(!UnimplementedFeature::setDLLStorageClass()); + return BuildTypeInfo(loc, Ty, Linkage, symVisibility); +} + +void CIRGenItaniumRTTIBuilder::BuildVTablePointer(mlir::Location loc, + const Type *Ty) { + auto &builder = CGM.getBuilder(); + + // abi::__class_type_info. + static const char *const ClassTypeInfo = + "_ZTVN10__cxxabiv117__class_type_infoE"; + // abi::__si_class_type_info. + static const char *const SIClassTypeInfo = + "_ZTVN10__cxxabiv120__si_class_type_infoE"; + // abi::__vmi_class_type_info. + static const char *const VMIClassTypeInfo = + "_ZTVN10__cxxabiv121__vmi_class_type_infoE"; + + const char *VTableName = nullptr; + + switch (Ty->getTypeClass()) { +#define TYPE(Class, Base) +#define ABSTRACT_TYPE(Class, Base) +#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(Class, Base) case Type::Class: +#define NON_CANONICAL_TYPE(Class, Base) case Type::Class: +#define DEPENDENT_TYPE(Class, Base) case Type::Class: +#include "clang/AST/TypeNodes.inc" + llvm_unreachable("Non-canonical and dependent types shouldn't get here"); + + case Type::LValueReference: + case Type::RValueReference: + llvm_unreachable("References shouldn't get here"); + + case Type::Auto: + case Type::DeducedTemplateSpecialization: + llvm_unreachable("Undeduced type shouldn't get here"); + + case Type::Pipe: + llvm_unreachable("Pipe types shouldn't get here"); + + case Type::Builtin: + case Type::BitInt: + // GCC treats vector and complex types as fundamental types. + case Type::Vector: + case Type::ExtVector: + case Type::ConstantMatrix: + case Type::Complex: + case Type::Atomic: + // FIXME: GCC treats block pointers as fundamental types?! + case Type::BlockPointer: + // abi::__fundamental_type_info. + VTableName = "_ZTVN10__cxxabiv123__fundamental_type_infoE"; + break; + + case Type::ConstantArray: + case Type::IncompleteArray: + case Type::VariableArray: + // abi::__array_type_info. + VTableName = "_ZTVN10__cxxabiv117__array_type_infoE"; + break; + + case Type::FunctionNoProto: + case Type::FunctionProto: + // abi::__function_type_info. + VTableName = "_ZTVN10__cxxabiv120__function_type_infoE"; + break; + + case Type::Enum: + // abi::__enum_type_info. + VTableName = "_ZTVN10__cxxabiv116__enum_type_infoE"; + break; + + case Type::Record: { + const CXXRecordDecl *RD = + cast(cast(Ty)->getDecl()); + + if (!RD->hasDefinition() || !RD->getNumBases()) { + VTableName = ClassTypeInfo; + } else if (CanUseSingleInheritance(RD)) { + VTableName = SIClassTypeInfo; + } else { + VTableName = VMIClassTypeInfo; + } + + break; + } + + case Type::ObjCObject: + // Ignore protocol qualifiers. + Ty = cast(Ty)->getBaseType().getTypePtr(); + + // Handle id and Class. + if (isa(Ty)) { + VTableName = ClassTypeInfo; + break; + } + + assert(isa(Ty)); + [[fallthrough]]; + + case Type::ObjCInterface: + if (cast(Ty)->getDecl()->getSuperClass()) { + VTableName = SIClassTypeInfo; + } else { + VTableName = ClassTypeInfo; + } + break; + + case Type::ObjCObjectPointer: + case Type::Pointer: + // abi::__pointer_type_info. + VTableName = "_ZTVN10__cxxabiv119__pointer_type_infoE"; + break; + + case Type::MemberPointer: + // abi::__pointer_to_member_type_info. + VTableName = "_ZTVN10__cxxabiv129__pointer_to_member_type_infoE"; + break; + } + + mlir::cir::GlobalOp VTable{}; + + // Check if the alias exists. If it doesn't, then get or create the global. + if (CGM.getItaniumVTableContext().isRelativeLayout()) + llvm_unreachable("NYI"); + if (!VTable) { + VTable = CGM.getOrInsertGlobal(loc, VTableName, + CGM.getBuilder().getUInt8PtrTy()); + } + + assert(!UnimplementedFeature::setDSOLocal()); + auto PtrDiffTy = + CGM.getTypes().ConvertType(CGM.getASTContext().getPointerDiffType()); + + // The vtable address point is 2. + mlir::Attribute field{}; + if (CGM.getItaniumVTableContext().isRelativeLayout()) { + llvm_unreachable("NYI"); + } else { + SmallVector offsets{ + mlir::cir::IntAttr::get(PtrDiffTy, 2)}; + auto indices = mlir::ArrayAttr::get(builder.getContext(), offsets); + field = CGM.getBuilder().getGlobalViewAttr(CGM.getBuilder().getUInt8PtrTy(), + VTable, indices); + } + + assert(field && "expected attribute"); + Fields.push_back(field); +} + +mlir::cir::GlobalOp CIRGenItaniumRTTIBuilder::GetAddrOfTypeName( + mlir::Location loc, QualType Ty, mlir::cir::GlobalLinkageKind Linkage) { + auto &builder = CGM.getBuilder(); + SmallString<256> Name; + llvm::raw_svector_ostream Out(Name); + CGM.getCXXABI().getMangleContext().mangleCXXRTTIName(Ty, Out); + + // We know that the mangled name of the type starts at index 4 of the + // mangled name of the typename, so we can just index into it in order to + // get the mangled name of the type. + auto Init = builder.getString( + Name.substr(4), CGM.getTypes().ConvertType(CGM.getASTContext().CharTy)); + auto Align = + CGM.getASTContext().getTypeAlignInChars(CGM.getASTContext().CharTy); + + auto GV = CGM.createOrReplaceCXXRuntimeVariable(loc, Name, Init.getType(), + Linkage, Align); + CIRGenModule::setInitializer(GV, Init); + return GV; +} + +/// Build an abi::__si_class_type_info, used for single inheritance, according +/// to the Itanium C++ ABI, 2.95p6b. +void CIRGenItaniumRTTIBuilder::BuildSIClassTypeInfo(mlir::Location loc, + const CXXRecordDecl *RD) { + // Itanium C++ ABI 2.9.5p6b: + // It adds to abi::__class_type_info a single member pointing to the + // type_info structure for the base type, + auto BaseTypeInfo = CIRGenItaniumRTTIBuilder(CXXABI, CGM) + .BuildTypeInfo(loc, RD->bases_begin()->getType()); + Fields.push_back(BaseTypeInfo); +} + +namespace { +/// Contains virtual and non-virtual bases seen when traversing a class +/// hierarchy. +struct SeenBases { + llvm::SmallPtrSet NonVirtualBases; + llvm::SmallPtrSet VirtualBases; +}; +} // namespace + +/// Compute the value of the flags member in abi::__vmi_class_type_info. +/// +static unsigned ComputeVMIClassTypeInfoFlags(const CXXBaseSpecifier *Base, + SeenBases &Bases) { + + unsigned Flags = 0; + + auto *BaseDecl = + cast(Base->getType()->castAs()->getDecl()); + + if (Base->isVirtual()) { + // Mark the virtual base as seen. + if (!Bases.VirtualBases.insert(BaseDecl).second) { + // If this virtual base has been seen before, then the class is diamond + // shaped. + Flags |= CIRGenItaniumRTTIBuilder::VMI_DiamondShaped; + } else { + if (Bases.NonVirtualBases.count(BaseDecl)) + Flags |= CIRGenItaniumRTTIBuilder::VMI_NonDiamondRepeat; + } + } else { + // Mark the non-virtual base as seen. + if (!Bases.NonVirtualBases.insert(BaseDecl).second) { + // If this non-virtual base has been seen before, then the class has non- + // diamond shaped repeated inheritance. + Flags |= CIRGenItaniumRTTIBuilder::VMI_NonDiamondRepeat; + } else { + if (Bases.VirtualBases.count(BaseDecl)) + Flags |= CIRGenItaniumRTTIBuilder::VMI_NonDiamondRepeat; + } + } + + // Walk all bases. + for (const auto &I : BaseDecl->bases()) + Flags |= ComputeVMIClassTypeInfoFlags(&I, Bases); + + return Flags; +} + +static unsigned ComputeVMIClassTypeInfoFlags(const CXXRecordDecl *RD) { + unsigned Flags = 0; + SeenBases Bases; + + // Walk all bases. + for (const auto &I : RD->bases()) + Flags |= ComputeVMIClassTypeInfoFlags(&I, Bases); + + return Flags; +} + +/// Build an abi::__vmi_class_type_info, used for +/// classes with bases that do not satisfy the abi::__si_class_type_info +/// constraints, according to the Itanium C++ ABI, 2.9.5p5c. +void CIRGenItaniumRTTIBuilder::BuildVMIClassTypeInfo(mlir::Location loc, + const CXXRecordDecl *RD) { + auto UnsignedIntLTy = + CGM.getTypes().ConvertType(CGM.getASTContext().UnsignedIntTy); + // Itanium C++ ABI 2.9.5p6c: + // __flags is a word with flags describing details about the class + // structure, which may be referenced by using the __flags_masks + // enumeration. These flags refer to both direct and indirect bases. + unsigned Flags = ComputeVMIClassTypeInfoFlags(RD); + Fields.push_back(mlir::cir::IntAttr::get(UnsignedIntLTy, Flags)); + + // Itanium C++ ABI 2.9.5p6c: + // __base_count is a word with the number of direct proper base class + // descriptions that follow. + Fields.push_back(mlir::cir::IntAttr::get(UnsignedIntLTy, RD->getNumBases())); + + if (!RD->getNumBases()) + return; + + // Now add the base class descriptions. + + // Itanium C++ ABI 2.9.5p6c: + // __base_info[] is an array of base class descriptions -- one for every + // direct proper base. Each description is of the type: + // + // struct abi::__base_class_type_info { + // public: + // const __class_type_info *__base_type; + // long __offset_flags; + // + // enum __offset_flags_masks { + // __virtual_mask = 0x1, + // __public_mask = 0x2, + // __offset_shift = 8 + // }; + // }; + + // If we're in mingw and 'long' isn't wide enough for a pointer, use 'long + // long' instead of 'long' for __offset_flags. libstdc++abi uses long long on + // LLP64 platforms. + // FIXME: Consider updating libc++abi to match, and extend this logic to all + // LLP64 platforms. + QualType OffsetFlagsTy = CGM.getASTContext().LongTy; + const TargetInfo &TI = CGM.getASTContext().getTargetInfo(); + if (TI.getTriple().isOSCygMing() && + TI.getPointerWidth(LangAS::Default) > TI.getLongWidth()) + OffsetFlagsTy = CGM.getASTContext().LongLongTy; + auto OffsetFlagsLTy = CGM.getTypes().ConvertType(OffsetFlagsTy); + + for (const auto &Base : RD->bases()) { + // The __base_type member points to the RTTI for the base type. + Fields.push_back( + CIRGenItaniumRTTIBuilder(CXXABI, CGM).BuildTypeInfo(loc, Base.getType())); + + auto *BaseDecl = + cast(Base.getType()->castAs()->getDecl()); + + int64_t OffsetFlags = 0; + + // All but the lower 8 bits of __offset_flags are a signed offset. + // For a non-virtual base, this is the offset in the object of the base + // subobject. For a virtual base, this is the offset in the virtual table of + // the virtual base offset for the virtual base referenced (negative). + CharUnits Offset; + if (Base.isVirtual()) + Offset = CGM.getItaniumVTableContext().getVirtualBaseOffsetOffset( + RD, BaseDecl); + else + llvm_unreachable("Multi-inheritence NYI"); + + OffsetFlags = uint64_t(Offset.getQuantity()) << 8; + + // The low-order byte of __offset_flags contains flags, as given by the + // masks from the enumeration __offset_flags_masks. + if (Base.isVirtual()) + OffsetFlags |= BCTI_Virtual; + if (Base.getAccessSpecifier() == AS_public) + OffsetFlags |= BCTI_Public; + + Fields.push_back(mlir::cir::IntAttr::get(OffsetFlagsLTy, OffsetFlags)); + } +} + +mlir::Attribute +CIRGenItaniumRTTIBuilder::GetAddrOfExternalRTTIDescriptor(mlir::Location loc, + QualType Ty) { + // Mangle the RTTI name. + SmallString<256> Name; + llvm::raw_svector_ostream Out(Name); + CGM.getCXXABI().getMangleContext().mangleCXXRTTI(Ty, Out); + auto &builder = CGM.getBuilder(); + + // Look for an existing global. + auto GV = dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(CGM.getModule(), Name)); + + if (!GV) { + // Create a new global variable. + // From LLVM codegen => Note for the future: If we would ever like to do + // deferred emission of RTTI, check if emitting vtables opportunistically + // need any adjustment. + GV = CIRGenModule::createGlobalOp(CGM, loc, Name, builder.getUInt8PtrTy(), + /*isConstant=*/true); + const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl(); + CGM.setGVProperties(GV, RD); + + // Import the typeinfo symbol when all non-inline virtual methods are + // imported. + if (CGM.getTarget().hasPS4DLLImportExport()) + llvm_unreachable("NYI"); + } + + return builder.getGlobalViewAttr(builder.getUInt8PtrTy(), GV); +} + +mlir::Attribute CIRGenItaniumRTTIBuilder::BuildTypeInfo( + mlir::Location loc, QualType Ty, mlir::cir::GlobalLinkageKind Linkage, + mlir::SymbolTable::Visibility Visibility) { + auto &builder = CGM.getBuilder(); + assert(!UnimplementedFeature::setDLLStorageClass()); + + // Add the vtable pointer. + BuildVTablePointer(loc, cast(Ty)); + + // And the name. + auto TypeName = GetAddrOfTypeName(loc, Ty, Linkage); + mlir::Attribute TypeNameField; + + // If we're supposed to demote the visibility, be sure to set a flag + // to use a string comparison for type_info comparisons. + CIRGenItaniumCXXABI::RTTIUniquenessKind RTTIUniqueness = + CXXABI.classifyRTTIUniqueness(Ty, Linkage); + if (RTTIUniqueness != CIRGenItaniumCXXABI::RUK_Unique) { + // The flag is the sign bit, which on ARM64 is defined to be clear + // for global pointers. This is very ARM64-specific. + llvm_unreachable("NYI"); + } else { + TypeNameField = + builder.getGlobalViewAttr(builder.getUInt8PtrTy(), TypeName); + } + Fields.push_back(TypeNameField); + + switch (Ty->getTypeClass()) { +#define TYPE(Class, Base) +#define ABSTRACT_TYPE(Class, Base) +#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(Class, Base) case Type::Class: +#define NON_CANONICAL_TYPE(Class, Base) case Type::Class: +#define DEPENDENT_TYPE(Class, Base) case Type::Class: +#include "clang/AST/TypeNodes.inc" + llvm_unreachable("Non-canonical and dependent types shouldn't get here"); + + // GCC treats vector types as fundamental types. + case Type::Builtin: + case Type::Vector: + case Type::ExtVector: + case Type::ConstantMatrix: + case Type::Complex: + case Type::BlockPointer: + // Itanium C++ ABI 2.9.5p4: + // abi::__fundamental_type_info adds no data members to std::type_info. + break; + + case Type::LValueReference: + case Type::RValueReference: + llvm_unreachable("References shouldn't get here"); + + case Type::Auto: + case Type::DeducedTemplateSpecialization: + llvm_unreachable("Undeduced type shouldn't get here"); + + case Type::Pipe: + break; + + case Type::BitInt: + break; + + case Type::ConstantArray: + case Type::IncompleteArray: + case Type::VariableArray: + // Itanium C++ ABI 2.9.5p5: + // abi::__array_type_info adds no data members to std::type_info. + break; + + case Type::FunctionNoProto: + case Type::FunctionProto: + // Itanium C++ ABI 2.9.5p5: + // abi::__function_type_info adds no data members to std::type_info. + break; + + case Type::Enum: + // Itanium C++ ABI 2.9.5p5: + // abi::__enum_type_info adds no data members to std::type_info. + break; + + case Type::Record: { + const CXXRecordDecl *RD = + cast(cast(Ty)->getDecl()); + if (!RD->hasDefinition() || !RD->getNumBases()) { + // We don't need to emit any fields. + break; + } + + if (CanUseSingleInheritance(RD)) { + BuildSIClassTypeInfo(loc, RD); + } else { + BuildVMIClassTypeInfo(loc, RD); + } + + break; + } + + case Type::ObjCObject: + case Type::ObjCInterface: + llvm_unreachable("NYI"); + break; + + case Type::ObjCObjectPointer: + llvm_unreachable("NYI"); + break; + + case Type::Pointer: + llvm_unreachable("NYI"); + break; + + case Type::MemberPointer: + llvm_unreachable("NYI"); + break; + + case Type::Atomic: + // No fields, at least for the moment. + break; + } + + assert(!UnimplementedFeature::setDLLImportDLLExport()); + auto init = builder.getTypeInfo(builder.getArrayAttr(Fields)); + + SmallString<256> Name; + llvm::raw_svector_ostream Out(Name); + CGM.getCXXABI().getMangleContext().mangleCXXRTTI(Ty, Out); + + // Create new global and search for an existing global. + auto OldGV = dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(CGM.getModule(), Name)); + mlir::cir::GlobalOp GV = + CIRGenModule::createGlobalOp(CGM, loc, Name, init.getType(), + /*isConstant=*/true); + + // Export the typeinfo in the same circumstances as the vtable is + // exported. + if (CGM.getTarget().hasPS4DLLImportExport()) + llvm_unreachable("NYI"); + + // If there's already an old global variable, replace it with the new one. + if (OldGV) { + // Replace occurrences of the old variable if needed. + GV.setName(OldGV.getName()); + if (!OldGV->use_empty()) { + // TODO: replaceAllUsesWith + llvm_unreachable("NYI"); + } + OldGV->erase(); + } + + if (CGM.supportsCOMDAT() && mlir::cir::isWeakForLinker(GV.getLinkage())) { + assert(!UnimplementedFeature::setComdat()); + llvm_unreachable("NYI"); + } + + CharUnits Align = CGM.getASTContext().toCharUnitsFromBits( + CGM.getTarget().getPointerAlign(LangAS::Default)); + GV.setAlignmentAttr(CGM.getSize(Align)); + + // The Itanium ABI specifies that type_info objects must be globally + // unique, with one exception: if the type is an incomplete class + // type or a (possibly indirect) pointer to one. That exception + // affects the general case of comparing type_info objects produced + // by the typeid operator, which is why the comparison operators on + // std::type_info generally use the type_info name pointers instead + // of the object addresses. However, the language's built-in uses + // of RTTI generally require class types to be complete, even when + // manipulating pointers to those class types. This allows the + // implementation of dynamic_cast to rely on address equality tests, + // which is much faster. + // + // All of this is to say that it's important that both the type_info + // object and the type_info name be uniqued when weakly emitted. + + // TODO(cir): setup other bits for TypeName + assert(!UnimplementedFeature::setDLLStorageClass()); + assert(!UnimplementedFeature::setPartition()); + assert(!UnimplementedFeature::setDSOLocal()); + mlir::SymbolTable::setSymbolVisibility( + TypeName, CIRGenModule::getMLIRVisibility(TypeName)); + + // TODO(cir): setup other bits for GV + assert(!UnimplementedFeature::setDLLStorageClass()); + assert(!UnimplementedFeature::setPartition()); + assert(!UnimplementedFeature::setDSOLocal()); + CIRGenModule::setInitializer(GV, init); + + return builder.getGlobalViewAttr(builder.getUInt8PtrTy(), GV);; +} + +mlir::Attribute CIRGenItaniumCXXABI::getAddrOfRTTIDescriptor(mlir::Location loc, + QualType Ty) { + return CIRGenItaniumRTTIBuilder(*this, CGM).BuildTypeInfo(loc, Ty); +} + +void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &CGVT, + const CXXRecordDecl *RD) { + auto VTable = getAddrOfVTable(RD, CharUnits()); + if (VTable.hasInitializer()) + return; + + ItaniumVTableContext &VTContext = CGM.getItaniumVTableContext(); + const VTableLayout &VTLayout = VTContext.getVTableLayout(RD); + auto Linkage = CGM.getVTableLinkage(RD); + auto RTTI = CGM.getAddrOfRTTIDescriptor( + CGM.getLoc(RD->getBeginLoc()), CGM.getASTContext().getTagDeclType(RD)); + + // Create and set the initializer. + ConstantInitBuilder builder(CGM); + auto components = builder.beginStruct(); + + CGVT.createVTableInitializer(components, VTLayout, RTTI, + mlir::cir::isLocalLinkage(Linkage)); + components.finishAndSetAsInitializer(VTable, /*forVtable=*/true); + + // Set the correct linkage. + VTable.setLinkage(Linkage); + + if (CGM.supportsCOMDAT() && mlir::cir::isWeakForLinker(Linkage)) { + assert(!UnimplementedFeature::setComdat()); + } + + // Set the right visibility. + CGM.setGVProperties(VTable, RD); + + // If this is the magic class __cxxabiv1::__fundamental_type_info, + // we will emit the typeinfo for the fundamental types. This is the + // same behaviour as GCC. + const DeclContext *DC = RD->getDeclContext(); + if (RD->getIdentifier() && + RD->getIdentifier()->isStr("__fundamental_type_info") && + isa(DC) && cast(DC)->getIdentifier() && + cast(DC)->getIdentifier()->isStr("__cxxabiv1") && + DC->getParent()->isTranslationUnit()) { + llvm_unreachable("NYI"); + // EmitFundamentalRTTIDescriptors(RD); + } + + // Always emit type metadata on non-available_externally definitions, and on + // available_externally definitions if we are performing whole program + // devirtualization. For WPD we need the type metadata on all vtable + // definitions to ensure we associate derived classes with base classes + // defined in headers but with a strong definition only in a shared + // library. + if (!VTable.isDeclarationForLinker() || + CGM.getCodeGenOpts().WholeProgramVTables) { + CGM.buildVTableTypeMetadata(RD, VTable, VTLayout); + // For available_externally definitions, add the vtable to + // @llvm.compiler.used so that it isn't deleted before whole program + // analysis. + if (VTable.isDeclarationForLinker()) { + llvm_unreachable("NYI"); + assert(CGM.getCodeGenOpts().WholeProgramVTables); + assert(!UnimplementedFeature::addCompilerUsedGlobal()); + } + } + + if (VTContext.isRelativeLayout()) + llvm_unreachable("NYI"); +} + +void CIRGenItaniumCXXABI::emitVirtualInheritanceTables( + const CXXRecordDecl *RD) { + CIRGenVTables &VTables = CGM.getVTables(); + auto VTT = VTables.getAddrOfVTT(RD); + VTables.buildVTTDefinition(VTT, CGM.getVTableLinkage(RD), RD); +} + +/// What sort of uniqueness rules should we use for the RTTI for the +/// given type? +CIRGenItaniumCXXABI::RTTIUniquenessKind +CIRGenItaniumCXXABI::classifyRTTIUniqueness( + QualType CanTy, mlir::cir::GlobalLinkageKind Linkage) const { + if (shouldRTTIBeUnique()) + return RUK_Unique; + + // It's only necessary for linkonce_odr or weak_odr linkage. + if (Linkage != mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage && + Linkage != mlir::cir::GlobalLinkageKind::WeakODRLinkage) + return RUK_Unique; + + // It's only necessary with default visibility. + if (CanTy->getVisibility() != DefaultVisibility) + return RUK_Unique; + + // If we're not required to publish this symbol, hide it. + if (Linkage == mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage) + return RUK_NonUniqueHidden; + + // If we're required to publish this symbol, as we might be under an + // explicit instantiation, leave it with default visibility but + // enable string-comparisons. + assert(Linkage == mlir::cir::GlobalLinkageKind::WeakODRLinkage); + return RUK_NonUniqueVisible; +} + +void CIRGenItaniumCXXABI::buildDestructorCall( + CIRGenFunction &CGF, const CXXDestructorDecl *DD, CXXDtorType Type, + bool ForVirtualBase, bool Delegating, Address This, QualType ThisTy) { + GlobalDecl GD(DD, Type); + auto VTT = + getCXXDestructorImplicitParam(CGF, DD, Type, ForVirtualBase, Delegating); + QualType VTTTy = getContext().getPointerType(getContext().VoidPtrTy); + CIRGenCallee Callee; + if (getContext().getLangOpts().AppleKext && Type != Dtor_Base && + DD->isVirtual()) + llvm_unreachable("NYI"); + else + Callee = CIRGenCallee::forDirect(CGM.getAddrOfCXXStructor(GD), GD); + + CGF.buildCXXDestructorCall(GD, Callee, This.getPointer(), ThisTy, VTT, VTTTy, + nullptr); +} + +mlir::Value CIRGenItaniumCXXABI::getCXXDestructorImplicitParam( + CIRGenFunction &CGF, const CXXDestructorDecl *DD, CXXDtorType Type, + bool ForVirtualBase, bool Delegating) { + GlobalDecl GD(DD, Type); + return CGF.GetVTTParameter(GD, ForVirtualBase, Delegating); +} + +void CIRGenItaniumCXXABI::buildRethrow(CIRGenFunction &CGF, bool isNoReturn) { + // void __cxa_rethrow(); + llvm_unreachable("NYI"); +} + +void CIRGenItaniumCXXABI::buildThrow(CIRGenFunction &CGF, + const CXXThrowExpr *E) { + // This differs a bit from LLVM codegen, CIR has native operations for some + // cxa functions, and defers allocation size computation, always pass the dtor + // symbol, etc. CIRGen also does not use getAllocateExceptionFn / getThrowFn. + + // Now allocate the exception object. + auto &builder = CGF.getBuilder(); + QualType clangThrowType = E->getSubExpr()->getType(); + auto throwTy = CGF.ConvertType(clangThrowType); + auto subExprLoc = CGF.getLoc(E->getSubExpr()->getSourceRange()); + // Defer computing allocation size to some later lowering pass. + auto exceptionPtr = + builder + .create( + subExprLoc, builder.getPointerTo(throwTy), throwTy) + .getAddr(); + + // Build expression and store its result into exceptionPtr. + CharUnits exnAlign = CGF.getContext().getExnObjectAlignment(); + CGF.buildAnyExprToExn(E->getSubExpr(), Address(exceptionPtr, exnAlign)); + + // Get the RTTI symbol address. + auto typeInfo = CGM.getAddrOfRTTIDescriptor(subExprLoc, clangThrowType, + /*ForEH=*/true) + .dyn_cast_or_null(); + assert(typeInfo && "expected GlobalViewAttr typeinfo"); + assert(!typeInfo.getIndices() && "expected no indirection"); + + // The address of the destructor. + // + // Note: LLVM codegen already optimizes out the dtor if the + // type is a record with trivial dtor (by passing down a + // null dtor). In CIR, we forward this info and allow for + // LoweringPrepare or some other pass to skip passing the + // trivial function. + // + // TODO(cir): alternatively, dtor could be ignored here and + // the type used to gather the relevant dtor during + // LoweringPrepare. + mlir::FlatSymbolRefAttr dtor{}; + if (const RecordType *recordTy = clangThrowType->getAs()) { + CXXRecordDecl *rec = cast(recordTy->getDecl()); + CXXDestructorDecl *dtorD = rec->getDestructor(); + dtor = mlir::FlatSymbolRefAttr::get( + CGM.getAddrOfCXXStructor(GlobalDecl(dtorD, Dtor_Complete)) + .getSymNameAttr()); + } + + assert(!CGF.getInvokeDest() && "landing pad like logic NYI"); + + // Now throw the exception. + builder.create(CGF.getLoc(E->getSourceRange()), + exceptionPtr, typeInfo.getSymbol(), dtor); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp new file mode 100644 index 000000000000..77473e50e532 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -0,0 +1,2623 @@ +//===- CIRGenModule.cpp - Per-Module state for CIR generation -------------===// +// +// 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 is the internal per-translation-unit state used for CIR translation. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenModule.h" + +#include "CIRGenCXXABI.h" +#include "CIRGenCstEmitter.h" +#include "CIRGenFunction.h" +#include "CIRGenTypes.h" +#include "CIRGenValue.h" +#include "TargetInfo.h" + +#include "UnimplementedFeatureGuarding.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/IR/Attributes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/OperationSupport.h" +#include "mlir/IR/SymbolTable.h" +#include "mlir/IR/Verifier.h" + +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclGroup.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/EvaluatedExprVisitor.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/ParentMap.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtCXX.h" +#include "clang/AST/StmtObjC.h" +#include "clang/AST/Type.h" +#include "clang/Basic/Diagnostic.h" +#include "clang/Basic/LangStandard.h" +#include "clang/Basic/NoSanitizeList.h" +#include "clang/Basic/SourceLocation.h" +#include "clang/CIR/CIRGenerator.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/LowerToLLVM.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Lex/Preprocessor.h" + +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/BitVector.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/ScopedHashTable.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include + +using namespace mlir::cir; +using namespace cir; +using namespace clang; + +using llvm::cast; +using llvm::dyn_cast; +using llvm::isa; +using llvm::SmallVector; +using llvm::StringRef; + +static CIRGenCXXABI *createCXXABI(CIRGenModule &CGM) { + switch (CGM.getASTContext().getCXXABIKind()) { + case TargetCXXABI::GenericItanium: + case TargetCXXABI::GenericAArch64: + case TargetCXXABI::AppleARM64: + return CreateCIRGenItaniumCXXABI(CGM); + default: + llvm_unreachable("invalid C++ ABI kind"); + } +} + +CIRGenModule::CIRGenModule(mlir::MLIRContext &context, + clang::ASTContext &astctx, + const clang::CodeGenOptions &CGO, + DiagnosticsEngine &Diags) + : builder(context, *this), astCtx(astctx), langOpts(astctx.getLangOpts()), + codeGenOpts(CGO), + theModule{mlir::ModuleOp::create(builder.getUnknownLoc())}, Diags(Diags), + target(astCtx.getTargetInfo()), ABI(createCXXABI(*this)), genTypes{*this}, + VTables{*this} { + + // Initialize CIR signed integer types cache. + SInt8Ty = + ::mlir::cir::IntType::get(builder.getContext(), 8, /*isSigned=*/true); + SInt16Ty = + ::mlir::cir::IntType::get(builder.getContext(), 16, /*isSigned=*/true); + SInt32Ty = + ::mlir::cir::IntType::get(builder.getContext(), 32, /*isSigned=*/true); + SInt64Ty = + ::mlir::cir::IntType::get(builder.getContext(), 64, /*isSigned=*/true); + + // Initialize CIR unsigned integer types cache. + UInt8Ty = + ::mlir::cir::IntType::get(builder.getContext(), 8, /*isSigned=*/false); + UInt16Ty = + ::mlir::cir::IntType::get(builder.getContext(), 16, /*isSigned=*/false); + UInt32Ty = + ::mlir::cir::IntType::get(builder.getContext(), 32, /*isSigned=*/false); + UInt64Ty = + ::mlir::cir::IntType::get(builder.getContext(), 64, /*isSigned=*/false); + + VoidTy = ::mlir::cir::VoidType::get(builder.getContext()); + + // Initialize CIR pointer types cache. + VoidPtrTy = ::mlir::cir::PointerType::get(builder.getContext(), VoidTy); + + // TODO: HalfTy + // TODO: BFloatTy + FloatTy = builder.getF32Type(); + DoubleTy = builder.getF64Type(); + // TODO(cir): perhaps we should abstract long double variations into a custom + // cir.long_double type. Said type would also hold the semantics for lowering. + LongDouble80BitsTy = builder.getF80Type(); + + // TODO: PointerWidthInBits + PointerAlignInBytes = + astctx + .toCharUnitsFromBits( + astctx.getTargetInfo().getPointerAlign(LangAS::Default)) + .getQuantity(); + // TODO: SizeSizeInBytes + // TODO: IntAlignInBytes + UCharTy = ::mlir::cir::IntType::get(builder.getContext(), + astCtx.getTargetInfo().getCharWidth(), + /*isSigned=*/false); + UIntTy = ::mlir::cir::IntType::get(builder.getContext(), + astCtx.getTargetInfo().getIntWidth(), + /*isSigned=*/false); + UIntPtrTy = ::mlir::cir::IntType::get( + builder.getContext(), astCtx.getTargetInfo().getMaxPointerWidth(), + /*isSigned=*/false); + UInt8PtrTy = builder.getPointerTo(UInt8Ty); + UInt8PtrPtrTy = builder.getPointerTo(UInt8PtrTy); + // TODO: AllocaInt8PtrTy + // TODO: GlobalsInt8PtrTy + // TODO: ConstGlobalsPtrTy + // TODO: ASTAllocaAddressSpace + + mlir::cir::sob::SignedOverflowBehavior sob; + switch (langOpts.getSignedOverflowBehavior()) { + case clang::LangOptions::SignedOverflowBehaviorTy::SOB_Defined: + sob = sob::SignedOverflowBehavior::defined; + break; + case clang::LangOptions::SignedOverflowBehaviorTy::SOB_Undefined: + sob = sob::SignedOverflowBehavior::undefined; + break; + case clang::LangOptions::SignedOverflowBehaviorTy::SOB_Trapping: + sob = sob::SignedOverflowBehavior::trapping; + break; + } + theModule->setAttr("cir.sob", + mlir::cir::SignedOverflowBehaviorAttr::get(&context, sob)); + theModule->setAttr( + "cir.lang", mlir::cir::LangAttr::get(&context, getCIRSourceLanguage())); + // Set the module name to be the name of the main file. TranslationUnitDecl + // often contains invalid source locations and isn't a reliable source for the + // module location. + auto MainFileID = astctx.getSourceManager().getMainFileID(); + const FileEntry &MainFile = + *astctx.getSourceManager().getFileEntryForID(MainFileID); + auto Path = MainFile.tryGetRealPathName(); + if (!Path.empty()) { + theModule.setSymName(Path); + theModule->setLoc(mlir::FileLineColLoc::get(&context, Path, + /*line=*/0, + /*col=*/0)); + } +} + +CIRGenModule::~CIRGenModule() {} + +bool CIRGenModule::isTypeConstant(QualType Ty, bool ExcludeCtor, + bool ExcludeDtor) { + if (!Ty.isConstant(astCtx) && !Ty->isReferenceType()) + return false; + + if (astCtx.getLangOpts().CPlusPlus) { + if (const CXXRecordDecl *Record = + astCtx.getBaseElementType(Ty)->getAsCXXRecordDecl()) + return ExcludeCtor && !Record->hasMutableFields() && + (Record->hasTrivialDestructor() || ExcludeDtor); + } + + return true; +} + +/// FIXME: this could likely be a common helper and not necessarily related +/// with codegen. +/// Return the best known alignment for an unknown pointer to a +/// particular class. +CharUnits CIRGenModule::getClassPointerAlignment(const CXXRecordDecl *RD) { + if (!RD->hasDefinition()) + return CharUnits::One(); // Hopefully won't be used anywhere. + + auto &layout = astCtx.getASTRecordLayout(RD); + + // If the class is final, then we know that the pointer points to an + // object of that type and can use the full alignment. + if (RD->isEffectivelyFinal()) + return layout.getAlignment(); + + // Otherwise, we have to assume it could be a subclass. + return layout.getNonVirtualAlignment(); +} + +/// FIXME: this could likely be a common helper and not necessarily related +/// with codegen. +/// TODO: Add TBAAAccessInfo +CharUnits +CIRGenModule::getNaturalPointeeTypeAlignment(QualType T, + LValueBaseInfo *BaseInfo) { + return getNaturalTypeAlignment(T->getPointeeType(), BaseInfo, + /* forPointeeType= */ true); +} + +/// FIXME: this could likely be a common helper and not necessarily related +/// with codegen. +/// TODO: Add TBAAAccessInfo +CharUnits CIRGenModule::getNaturalTypeAlignment(QualType T, + LValueBaseInfo *BaseInfo, + bool forPointeeType) { + // FIXME: This duplicates logic in ASTContext::getTypeAlignIfKnown. But + // that doesn't return the information we need to compute BaseInfo. + + // Honor alignment typedef attributes even on incomplete types. + // We also honor them straight for C++ class types, even as pointees; + // there's an expressivity gap here. + if (auto TT = T->getAs()) { + if (auto Align = TT->getDecl()->getMaxAlignment()) { + if (BaseInfo) + *BaseInfo = LValueBaseInfo(AlignmentSource::AttributedType); + return astCtx.toCharUnitsFromBits(Align); + } + } + + bool AlignForArray = T->isArrayType(); + + // Analyze the base element type, so we don't get confused by incomplete + // array types. + T = astCtx.getBaseElementType(T); + + if (T->isIncompleteType()) { + // We could try to replicate the logic from + // ASTContext::getTypeAlignIfKnown, but nothing uses the alignment if the + // type is incomplete, so it's impossible to test. We could try to reuse + // getTypeAlignIfKnown, but that doesn't return the information we need + // to set BaseInfo. So just ignore the possibility that the alignment is + // greater than one. + if (BaseInfo) + *BaseInfo = LValueBaseInfo(AlignmentSource::Type); + return CharUnits::One(); + } + + if (BaseInfo) + *BaseInfo = LValueBaseInfo(AlignmentSource::Type); + + CharUnits Alignment; + const CXXRecordDecl *RD; + if (T.getQualifiers().hasUnaligned()) { + Alignment = CharUnits::One(); + } else if (forPointeeType && !AlignForArray && + (RD = T->getAsCXXRecordDecl())) { + // For C++ class pointees, we don't know whether we're pointing at a + // base or a complete object, so we generally need to use the + // non-virtual alignment. + Alignment = getClassPointerAlignment(RD); + } else { + Alignment = astCtx.getTypeAlignInChars(T); + } + + // Cap to the global maximum type alignment unless the alignment + // was somehow explicit on the type. + if (unsigned MaxAlign = astCtx.getLangOpts().MaxTypeAlign) { + if (Alignment.getQuantity() > MaxAlign && !astCtx.isAlignmentRequired(T)) + Alignment = CharUnits::fromQuantity(MaxAlign); + } + return Alignment; +} + +bool CIRGenModule::MustBeEmitted(const ValueDecl *Global) { + // Never defer when EmitAllDecls is specified. + assert(!langOpts.EmitAllDecls && "EmitAllDecls NYI"); + assert(!codeGenOpts.KeepStaticConsts && "KeepStaticConsts NYI"); + + return getASTContext().DeclMustBeEmitted(Global); +} + +bool CIRGenModule::MayBeEmittedEagerly(const ValueDecl *Global) { + assert(!langOpts.OpenMP && "NYI"); + + const auto *FD = dyn_cast(Global); + if (FD) { + // Implicit template instantiations may change linkage if they are later + // explicitly instantiated, so they should not be emitted eagerly. + // TODO(cir): do we care? + assert(FD->getTemplateSpecializationKind() != TSK_ImplicitInstantiation && + "not implemented"); + assert(!FD->isTemplated() && "Templates NYI"); + } + const auto *VD = dyn_cast(Global); + if (VD) + // A definition of an inline constexpr static data member may change + // linkage later if it's redeclared outside the class. + // TODO(cir): do we care? + assert(astCtx.getInlineVariableDefinitionKind(VD) != + ASTContext::InlineVariableDefinitionKind::WeakUnknown && + "not implemented"); + + assert((FD || VD) && + "Only FunctionDecl and VarDecl should hit this path so far."); + return true; +} + +void CIRGenModule::buildGlobal(GlobalDecl GD) { + const auto *Global = cast(GD.getDecl()); + + assert(!Global->hasAttr() && "NYI"); + assert(!Global->hasAttr() && "NYI"); + assert(!langOpts.CUDA && "NYI"); + assert(!langOpts.OpenMP && "NYI"); + + // Ignore declarations, they will be emitted on their first use. + if (const auto *FD = dyn_cast(Global)) { + // Forward declarations are emitted lazily on first use. + if (!FD->doesThisDeclarationHaveABody()) { + if (!FD->doesDeclarationForceExternallyVisibleDefinition()) + return; + + llvm::StringRef MangledName = getMangledName(GD); + + // Compute the function info and CIR type. + const auto &FI = getTypes().arrangeGlobalDeclaration(GD); + mlir::Type Ty = getTypes().GetFunctionType(FI); + + GetOrCreateCIRFunction(MangledName, Ty, GD, /*ForVTable=*/false, + /*DontDefer=*/false); + return; + } + } else { + const auto *VD = cast(Global); + assert(VD->isFileVarDecl() && "Cannot emit local var decl as global."); + if (VD->isThisDeclarationADefinition() != VarDecl::Definition && + !astCtx.isMSStaticDataMemberInlineDefinition(VD)) { + assert(!getLangOpts().OpenMP && "not implemented"); + // If this declaration may have caused an inline variable definition + // to change linkage, make sure that it's emitted. + // TODO(cir): probably use GetAddrOfGlobalVar(VD) below? + assert((astCtx.getInlineVariableDefinitionKind(VD) != + ASTContext::InlineVariableDefinitionKind::Strong) && + "not implemented"); + return; + } + } + + // Defer code generation to first use when possible, e.g. if this is an inline + // function. If the global mjust always be emitted, do it eagerly if possible + // to benefit from cache locality. + if (MustBeEmitted(Global) && MayBeEmittedEagerly(Global)) { + // Emit the definition if it can't be deferred. + buildGlobalDefinition(GD); + return; + } + + // If we're deferring emission of a C++ variable with an initializer, remember + // the order in which it appeared on the file. + if (getLangOpts().CPlusPlus && isa(Global) && + cast(Global)->hasInit()) { + DelayedCXXInitPosition[Global] = CXXGlobalInits.size(); + CXXGlobalInits.push_back(nullptr); + } + + llvm::StringRef MangledName = getMangledName(GD); + if (getGlobalValue(MangledName) != nullptr) { + // The value has already been used and should therefore be emitted. + addDeferredDeclToEmit(GD); + } else if (MustBeEmitted(Global)) { + // The value must be emitted, but cannot be emitted eagerly. + assert(!MayBeEmittedEagerly(Global)); + addDeferredDeclToEmit(GD); + } else { + // Otherwise, remember that we saw a deferred decl with this name. The first + // use of the mangled name will cause it to move into DeferredDeclsToEmit. + DeferredDecls[MangledName] = GD; + } +} + +void CIRGenModule::buildGlobalFunctionDefinition(GlobalDecl GD, + mlir::Operation *Op) { + auto const *D = cast(GD.getDecl()); + + // Compute the function info and CIR type. + const CIRGenFunctionInfo &FI = getTypes().arrangeGlobalDeclaration(GD); + auto Ty = getTypes().GetFunctionType(FI); + + // Get or create the prototype for the function. + // if (!V || (V.getValueType() != Ty)) + // TODO(cir): Figure out what to do here? llvm uses a GlobalValue for the + // FuncOp in mlir + Op = GetAddrOfFunction(GD, Ty, /*ForVTable=*/false, /*DontDefer=*/true, + ForDefinition); + + auto Fn = cast(Op); + // Already emitted. + if (!Fn.isDeclaration()) + return; + + setFunctionLinkage(GD, Fn); + setGVProperties(Op, D); + // TODO(cir): MaubeHandleStaticInExternC + // TODO(cir): maybeSetTrivialComdat + // TODO(cir): setLLVMFunctionFEnvAttributes + + CIRGenFunction CGF{*this, builder}; + CurCGF = &CGF; + { + mlir::OpBuilder::InsertionGuard guard(builder); + CGF.generateCode(GD, Fn, FI); + } + CurCGF = nullptr; + + // TODO: setNonAliasAttributes + // TODO: SetLLVMFunctionAttributesForDeclaration + + assert(!D->getAttr() && "NYI"); + assert(!D->getAttr() && "NYI"); + assert(!D->getAttr() && "NYI"); +} + +mlir::Operation *CIRGenModule::getGlobalValue(StringRef Name) { + auto global = mlir::SymbolTable::lookupSymbolIn(theModule, Name); + if (!global) + return {}; + return global; +} + +mlir::Value CIRGenModule::getGlobalValue(const Decl *D) { + assert(CurCGF); + return CurCGF->symbolTable.lookup(D); +} + +mlir::cir::GlobalOp CIRGenModule::createGlobalOp(CIRGenModule &CGM, + mlir::Location loc, + StringRef name, mlir::Type t, + bool isCst) { + mlir::cir::GlobalOp g; + auto &builder = CGM.getBuilder(); + { + mlir::OpBuilder::InsertionGuard guard(builder); + + // Some global emissions are triggered while emitting a function, e.g. + // void s() { const char *s = "yolo"; ... } + // + // Be sure to insert global before the current function + auto *curCGF = CGM.getCurrCIRGenFun(); + if (curCGF) + builder.setInsertionPoint(curCGF->CurFn); + + g = builder.create(loc, name, t, isCst); + if (!curCGF) + CGM.getModule().push_back(g); + + // Default to private until we can judge based on the initializer, + // since MLIR doesn't allow public declarations. + mlir::SymbolTable::setSymbolVisibility( + g, mlir::SymbolTable::Visibility::Private); + } + return g; +} + +void CIRGenModule::setCommonAttributes(GlobalDecl GD, mlir::Operation *GV) { + assert(!UnimplementedFeature::setCommonAttributes()); +} + +/// If the specified mangled name is not in the module, +/// create and return an mlir GlobalOp with the specified type (TODO(cir): +/// address space). +/// +/// TODO(cir): +/// 1. If there is something in the module with the specified name, return +/// it potentially bitcasted to the right type. +/// +/// 2. If D is non-null, it specifies a decl that correspond to this. This is +/// used to set the attributes on the global when it is first created. +/// +/// 3. If IsForDefinition is true, it is guaranteed that an actual global with +/// type Ty will be returned, not conversion of a variable with the same +/// mangled name but some other type. +mlir::cir::GlobalOp +CIRGenModule::getOrCreateCIRGlobal(StringRef MangledName, mlir::Type Ty, + LangAS AddrSpace, const VarDecl *D, + ForDefinition_t IsForDefinition) { + // Lookup the entry, lazily creating it if necessary. + mlir::cir::GlobalOp Entry; + if (auto *V = getGlobalValue(MangledName)) { + assert(isa(V) && "only supports GlobalOp for now"); + Entry = dyn_cast_or_null(V); + } + + // unsigned TargetAS = astCtx.getTargetAddressSpace(AddrSpace); + if (Entry) { + if (WeakRefReferences.erase(Entry)) { + if (D && !D->hasAttr()) { + auto LT = mlir::cir::GlobalLinkageKind::ExternalLinkage; + Entry.setLinkageAttr( + mlir::cir::GlobalLinkageKindAttr::get(builder.getContext(), LT)); + mlir::SymbolTable::setSymbolVisibility(Entry, getMLIRVisibility(Entry)); + } + } + + // Handle dropped DLL attributes. + if (D && !D->hasAttr() && + !D->hasAttr()) + assert(!UnimplementedFeature::setDLLStorageClass() && "NYI"); + + if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd && D) + assert(0 && "not implemented"); + + // TODO(cir): check TargetAS matches Entry address space + if (Entry.getSymType() == Ty && + !UnimplementedFeature::addressSpaceInGlobalVar()) + return Entry; + + // If there are two attempts to define the same mangled name, issue an + // error. + // + // TODO(cir): look at mlir::GlobalValue::isDeclaration for all aspects of + // recognizing the global as a declaration, for now only check if + // initializer is present. + if (IsForDefinition && !Entry.isDeclaration()) { + GlobalDecl OtherGD; + const VarDecl *OtherD; + + // Check that D is not yet in DiagnosedConflictingDefinitions is required + // to make sure that we issue an error only once. + if (D && lookupRepresentativeDecl(MangledName, OtherGD) && + (D->getCanonicalDecl() != OtherGD.getCanonicalDecl().getDecl()) && + (OtherD = dyn_cast(OtherGD.getDecl())) && + OtherD->hasInit() && + DiagnosedConflictingDefinitions.insert(D).second) { + getDiags().Report(D->getLocation(), diag::err_duplicate_mangled_name) + << MangledName; + getDiags().Report(OtherGD.getDecl()->getLocation(), + diag::note_previous_definition); + } + } + + // TODO(cir): LLVM codegen makes sure the result is of the correct type + // by issuing a address space cast. + + // TODO(cir): + // (In LLVM codgen, if global is requested for a definition, we always need + // to create a new global, otherwise return a bitcast.) + if (!IsForDefinition) + assert(0 && "not implemented"); + } + + // TODO(cir): auto DAddrSpace = GetGlobalVarAddressSpace(D); + // TODO(cir): do we need to strip pointer casts for Entry? + + auto loc = getLoc(D->getSourceRange()); + + // mlir::SymbolTable::Visibility::Public is the default, no need to explicitly + // mark it as such. + auto GV = CIRGenModule::createGlobalOp(*this, loc, MangledName, Ty, + /*isConstant=*/false); + + // If we already created a global with the same mangled name (but different + // type) before, take its name and remove it from its parent. + assert(!Entry && "not implemented"); + + // This is the first use or definition of a mangled name. If there is a + // deferred decl with this name, remember that we need to emit it at the end + // of the file. + auto DDI = DeferredDecls.find(MangledName); + if (DDI != DeferredDecls.end()) { + // Move the potentially referenced deferred decl to the DeferredDeclsToEmit + // list, and remove it from DeferredDecls (since we don't need it anymore). + addDeferredDeclToEmit(DDI->second); + DeferredDecls.erase(DDI); + } + + // Handle things which are present even on external declarations. + auto &LangOpts = getLangOpts(); + if (D) { + if (LangOpts.OpenMP && !LangOpts.OpenMPSimd) + assert(0 && "not implemented"); + + // FIXME: This code is overly simple and should be merged with other global + // handling. + + // TODO(cir): + // GV->setConstant(isTypeConstant(D->getType(), false)); + // GV->setAlignment(getContext().getDeclAlign(D).getAsAlign()); + // setLinkageForGV(GV, D); + + if (D->getTLSKind()) { + assert(0 && "not implemented"); + } + + setGVProperties(GV, D); + + // If required by the ABI, treat declarations of static data members with + // inline initializers as definitions. + if (astCtx.isMSStaticDataMemberInlineDefinition(D)) { + assert(0 && "not implemented"); + } + + // Emit section information for extern variables. + if (D->hasExternalStorage()) { + if (const SectionAttr *SA = D->getAttr()) { + assert(!UnimplementedFeature::setGlobalVarSection()); + llvm_unreachable("section info for extern vars is NYI"); + } + } + + // Handle XCore specific ABI requirements. + if (getTriple().getArch() == llvm::Triple::xcore) + assert(0 && "not implemented"); + + // Check if we a have a const declaration with an initializer, we maybe + // able to emit it as available_externally to expose it's value to the + // optimizer. + if (getLangOpts().CPlusPlus && GV.isPublic() && + D->getType().isConstQualified() && GV.isDeclaration() && + !D->hasDefinition() && D->hasInit() && !D->hasAttr()) { + assert(0 && "not implemented"); + } + } + + // TODO(cir): if this method is used to handle functions we must have + // something closer to GlobalValue::isDeclaration instead of checking for + // initializer. + if (GV.isDeclaration()) { + // TODO(cir): set target attributes + + // External HIP managed variables needed to be recorded for transformation + // in both device and host compilations. + if (getLangOpts().CUDA) + assert(0 && "not implemented"); + } + + // TODO(cir): address space cast when needed for DAddrSpace. + return GV; +} + +mlir::cir::GlobalOp CIRGenModule::buildGlobal(const VarDecl *D, mlir::Type Ty, + ForDefinition_t IsForDefinition) { + assert(D->hasGlobalStorage() && "Not a global variable"); + QualType ASTTy = D->getType(); + if (!Ty) + Ty = getTypes().convertTypeForMem(ASTTy); + + StringRef MangledName = getMangledName(D); + return getOrCreateCIRGlobal(MangledName, Ty, ASTTy.getAddressSpace(), D, + IsForDefinition); +} + +/// Return the mlir::Value for the address of the given global variable. If Ty +/// is non-null and if the global doesn't exist, then it will be created with +/// the specified type instead of whatever the normal requested type would be. +/// If IsForDefinition is true, it is guaranteed that an actual global with type +/// Ty will be returned, not conversion of a variable with the same mangled name +/// but some other type. +mlir::Value CIRGenModule::getAddrOfGlobalVar(const VarDecl *D, mlir::Type Ty, + ForDefinition_t IsForDefinition) { + assert(D->hasGlobalStorage() && "Not a global variable"); + QualType ASTTy = D->getType(); + if (!Ty) + Ty = getTypes().convertTypeForMem(ASTTy); + + auto g = buildGlobal(D, Ty, IsForDefinition); + auto ptrTy = + mlir::cir::PointerType::get(builder.getContext(), g.getSymType()); + return builder.create(getLoc(D->getSourceRange()), + ptrTy, g.getSymName()); +} + +mlir::cir::GlobalViewAttr +CIRGenModule::getAddrOfGlobalVarAttr(const VarDecl *D, mlir::Type Ty, + ForDefinition_t IsForDefinition) { + assert(D->hasGlobalStorage() && "Not a global variable"); + QualType ASTTy = D->getType(); + if (!Ty) + Ty = getTypes().convertTypeForMem(ASTTy); + + auto globalOp = buildGlobal(D, Ty, IsForDefinition); + return builder.getGlobalViewAttr(builder.getPointerTo(Ty), globalOp); +} + +mlir::Operation *CIRGenModule::getWeakRefReference(const ValueDecl *VD) { + const AliasAttr *AA = VD->getAttr(); + assert(AA && "No alias?"); + + // See if there is already something with the target's name in the module. + mlir::Operation *Entry = getGlobalValue(AA->getAliasee()); + if (Entry) { + assert((isa(Entry) || isa(Entry)) && + "weak ref should be against a global variable or function"); + return Entry; + } + + mlir::Type DeclTy = getTypes().convertTypeForMem(VD->getType()); + if (DeclTy.isa()) { + auto F = GetOrCreateCIRFunction(AA->getAliasee(), DeclTy, + GlobalDecl(cast(VD)), + /*ForVtable=*/false); + F.setLinkage(mlir::cir::GlobalLinkageKind::ExternalWeakLinkage); + WeakRefReferences.insert(F); + return F; + } + + llvm_unreachable("GlobalOp NYI"); +} + +/// TODO(cir): looks like part of this code can be part of a common AST +/// helper betweem CIR and LLVM codegen. +template +void CIRGenModule::maybeHandleStaticInExternC(const SomeDecl *D, + mlir::cir::GlobalOp GV) { + if (!getLangOpts().CPlusPlus) + return; + + // Must have 'used' attribute, or else inline assembly can't rely on + // the name existing. + if (!D->template hasAttr()) + return; + + // Must have internal linkage and an ordinary name. + if (!D->getIdentifier() || D->getFormalLinkage() != InternalLinkage) + return; + + // Must be in an extern "C" context. Entities declared directly within + // a record are not extern "C" even if the record is in such a context. + const SomeDecl *First = D->getFirstDecl(); + if (First->getDeclContext()->isRecord() || !First->isInExternCContext()) + return; + + // TODO(cir): + // OK, this is an internal linkage entity inside an extern "C" linkage + // specification. Make a note of that so we can give it the "expected" + // mangled name if nothing else is using that name. + // + // If we have multiple internal linkage entities with the same name + // in extern "C" regions, none of them gets that name. + assert(0 && "not implemented"); +} + +void CIRGenModule::buildGlobalVarDefinition(const clang::VarDecl *D, + bool IsTentative) { + // TODO(cir): + // OpenCL global variables of sampler type are translated to function calls, + // therefore no need to be translated. + // If this is OpenMP device, check if it is legal to emit this global + // normally. + QualType ASTTy = D->getType(); + assert(!(getLangOpts().OpenCL || getLangOpts().OpenMPIsTargetDevice) && + "not implemented"); + + // TODO(cir): LLVM's codegen uses a llvm::TrackingVH here. Is that + // necessary here for CIR gen? + mlir::Attribute Init; + bool NeedsGlobalCtor = false; + // Whether the definition of the variable is available externally. + // If yes, we shouldn't emit the GloablCtor and GlobalDtor for the variable + // since this is the job for its original source. + bool IsDefinitionAvailableExternally = + astCtx.GetGVALinkageForVariable(D) == GVA_AvailableExternally; + bool NeedsGlobalDtor = + !IsDefinitionAvailableExternally && + D->needsDestruction(astCtx) == QualType::DK_cxx_destructor; + + const VarDecl *InitDecl; + const Expr *InitExpr = D->getAnyInitializer(InitDecl); + + std::optional emitter; + + // CUDA E.2.4.1 "__shared__ variables cannot have an initialization + // as part of their declaration." Sema has already checked for + // error cases, so we just need to set Init to UndefValue. + bool IsCUDASharedVar = + getLangOpts().CUDAIsDevice && D->hasAttr(); + // Shadows of initialized device-side global variables are also left + // undefined. + // Managed Variables should be initialized on both host side and device side. + bool IsCUDAShadowVar = + !getLangOpts().CUDAIsDevice && !D->hasAttr() && + (D->hasAttr() || D->hasAttr() || + D->hasAttr()); + bool IsCUDADeviceShadowVar = + getLangOpts().CUDAIsDevice && !D->hasAttr() && + (D->getType()->isCUDADeviceBuiltinSurfaceType() || + D->getType()->isCUDADeviceBuiltinTextureType()); + if (getLangOpts().CUDA && + (IsCUDASharedVar || IsCUDAShadowVar || IsCUDADeviceShadowVar)) + assert(0 && "not implemented"); + else if (D->hasAttr()) + assert(0 && "not implemented"); + else if (!InitExpr) { + // This is a tentative definition; tentative definitions are + // implicitly initialized with { 0 }. + // + // Note that tentative definitions are only emitted at the end of + // a translation unit, so they should never have incomplete + // type. In addition, EmitTentativeDefinition makes sure that we + // never attempt to emit a tentative definition if a real one + // exists. A use may still exists, however, so we still may need + // to do a RAUW. + assert(!ASTTy->isIncompleteType() && "Unexpected incomplete type"); + Init = builder.getZeroInitAttr(getCIRType(D->getType())); + } else { + initializedGlobalDecl = GlobalDecl(D); + emitter.emplace(*this); + auto Initializer = emitter->tryEmitForInitializer(*InitDecl); + if (!Initializer) { + QualType T = InitExpr->getType(); + if (D->getType()->isReferenceType()) + T = D->getType(); + + if (getLangOpts().CPlusPlus) { + if (InitDecl->hasFlexibleArrayInit(astCtx)) + ErrorUnsupported(D, "flexible array initializer"); + Init = builder.getZeroInitAttr(getCIRType(T)); + if (!IsDefinitionAvailableExternally) + NeedsGlobalCtor = true; + } else { + ErrorUnsupported(D, "static initializer"); + } + } else { + Init = Initializer; + // We don't need an initializer, so remove the entry for the delayed + // initializer position (just in case this entry was delayed) if we + // also don't need to register a destructor. + if (getLangOpts().CPlusPlus && !NeedsGlobalDtor) + DelayedCXXInitPosition.erase(D); + } + } + + mlir::Type InitType; + // If the initializer attribute is a SymbolRefAttr it means we are + // initializing the global based on a global constant. + // + // TODO(cir): create another attribute to contain the final type and abstract + // away SymbolRefAttr. + if (auto symAttr = Init.dyn_cast()) { + auto cstGlobal = mlir::SymbolTable::lookupSymbolIn(theModule, symAttr); + assert(isa(cstGlobal) && + "unaware of other symbol providers"); + auto g = cast(cstGlobal); + auto arrayTy = g.getSymType().dyn_cast(); + // TODO(cir): pointer to array decay. Should this be modeled explicitly in + // CIR? + if (arrayTy) + InitType = mlir::cir::PointerType::get(builder.getContext(), + arrayTy.getEltType()); + } else { + assert(Init.isa() && "This should have a type"); + auto TypedInitAttr = Init.cast(); + InitType = TypedInitAttr.getType(); + } + assert(!InitType.isa() && "Should have a type by now"); + + auto Entry = buildGlobal(D, InitType, ForDefinition_t(!IsTentative)); + // TODO(cir): Strip off pointer casts from Entry if we get them? + + // TODO(cir): LLVM codegen used GlobalValue to handle both Function or + // GlobalVariable here. We currently only support GlobalOp, should this be + // used for FuncOp? + assert(dyn_cast(&Entry) && "FuncOp not supported here"); + auto GV = Entry; + + // We have a definition after a declaration with the wrong type. + // We must make a new GlobalVariable* and update everything that used OldGV + // (a declaration or tentative definition) with the new GlobalVariable* + // (which will be a definition). + // + // This happens if there is a prototype for a global (e.g. + // "extern int x[];") and then a definition of a different type (e.g. + // "int x[10];"). This also happens when an initializer has a different type + // from the type of the global (this happens with unions). + if (!GV || GV.getSymType() != InitType) { + // TODO(cir): this should include an address space check as well. + assert(0 && "not implemented"); + } + + maybeHandleStaticInExternC(D, GV); + + if (D->hasAttr()) + assert(0 && "not implemented"); + + // Set CIR's linkage type as appropriate. + mlir::cir::GlobalLinkageKind Linkage = + getCIRLinkageVarDefinition(D, /*IsConstant=*/false); + + // TODO(cir): + // CUDA B.2.1 "The __device__ qualifier declares a variable that resides on + // the device. [...]" + // CUDA B.2.2 "The __constant__ qualifier, optionally used together with + // __device__, declares a variable that: [...] + if (GV && getLangOpts().CUDA) { + assert(0 && "not implemented"); + } + + // Set initializer and finalize emission + CIRGenModule::setInitializer(GV, Init); + if (emitter) + emitter->finalize(GV); + + // TODO(cir): If it is safe to mark the global 'constant', do so now. + // GV->setConstant(!NeedsGlobalCtor && !NeedsGlobalDtor && + // isTypeConstant(D->getType(), true)); + + // If it is in a read-only section, mark it 'constant'. + if (const SectionAttr *SA = D->getAttr()) { + assert(0 && "not implemented"); + } + + // TODO(cir): + // GV->setAlignment(getContext().getDeclAlign(D).getAsAlign()); + + // On Darwin, unlike other Itanium C++ ABI platforms, the thread-wrapper + // function is only defined alongside the variable, not also alongside + // callers. Normally, all accesses to a thread_local go through the + // thread-wrapper in order to ensure initialization has occurred, underlying + // variable will never be used other than the thread-wrapper, so it can be + // converted to internal linkage. + // + // However, if the variable has the 'constinit' attribute, it _can_ be + // referenced directly, without calling the thread-wrapper, so the linkage + // must not be changed. + // + // Additionally, if the variable isn't plain external linkage, e.g. if it's + // weak or linkonce, the de-duplication semantics are important to preserve, + // so we don't change the linkage. + if (D->getTLSKind() == VarDecl::TLS_Dynamic && GV.isPublic() && + astCtx.getTargetInfo().getTriple().isOSDarwin() && + !D->hasAttr()) { + // TODO(cir): set to mlir::SymbolTable::Visibility::Private once we have + // testcases. + assert(0 && "not implemented"); + } + + // Set CIR linkage and DLL storage class. + GV.setLinkage(Linkage); + // FIXME(cir): setLinkage should likely set MLIR's visibility automatically. + GV.setVisibility(getMLIRVisibilityFromCIRLinkage(Linkage)); + // TODO(cir): handle DLL storage classes in CIR? + if (D->hasAttr()) + assert(!UnimplementedFeature::setDLLStorageClass()); + else if (D->hasAttr()) + assert(!UnimplementedFeature::setDLLStorageClass()); + else + assert(!UnimplementedFeature::setDLLStorageClass()); + + if (Linkage == mlir::cir::GlobalLinkageKind::CommonLinkage) { + llvm_unreachable("common linkage is NYI"); + } + + // TODO(cir): setNonAliasAttributes(D, GV); + + // TODO(cir): handle TLSKind if GV is not thread local + if (D->getTLSKind()) { // && !GV->isThreadLocal()) + assert(0 && "not implemented"); + } + + // TODO(cir): maybeSetTrivialComdat(*D, *GV); + + // TODO(cir): + // Emit the initializer function if necessary. + if (NeedsGlobalCtor || NeedsGlobalDtor) + buildGlobalVarDeclInit(D, GV, NeedsGlobalCtor); + + // TODO(cir): sanitizers (reportGlobalToASan) and global variable debug + // information. +} + +void CIRGenModule::buildGlobalDefinition(GlobalDecl GD, mlir::Operation *Op) { + const auto *D = cast(GD.getDecl()); + + if (const auto *FD = dyn_cast(D)) { + // At -O0, don't generate CIR for functions with available_externally + // linkage. + if (!shouldEmitFunction(GD)) + return; + + if (const auto *Method = dyn_cast(D)) { + // Make sure to emit the definition(s) before we emit the thunks. This is + // necessary for the generation of certain thunks. + if (isa(Method) || isa(Method)) + ABI->buildCXXStructor(GD); + else if (FD->isMultiVersion()) + llvm_unreachable("NYI"); + else + buildGlobalFunctionDefinition(GD, Op); + + if (Method->isVirtual()) + getVTables().buildThunks(GD); + + return; + } + + if (FD->isMultiVersion()) + llvm_unreachable("NYI"); + buildGlobalFunctionDefinition(GD, Op); + return; + } + + if (const auto *VD = dyn_cast(D)) + return buildGlobalVarDefinition(VD, !VD->hasDefinition()); + + llvm_unreachable("Invalid argument to buildGlobalDefinition()"); +} + +mlir::Attribute +CIRGenModule::getConstantArrayFromStringLiteral(const StringLiteral *E) { + assert(!E->getType()->isPointerType() && "Strings are always arrays"); + + // Don't emit it as the address of the string, emit the string data itself + // as an inline array. + if (E->getCharByteWidth() == 1) { + SmallString<64> Str(E->getString()); + + // Resize the string to the right size, which is indicated by its type. + const ConstantArrayType *CAT = astCtx.getAsConstantArrayType(E->getType()); + auto finalSize = CAT->getSize().getZExtValue(); + Str.resize(finalSize); + + auto eltTy = getTypes().ConvertType(CAT->getElementType()); + return builder.getString(Str, eltTy, finalSize); + } + + assert(0 && "not implemented"); + return {}; +} + +// TODO(cir): this could be a common AST helper for both CIR and LLVM codegen. +LangAS CIRGenModule::getGlobalConstantAddressSpace() const { + // OpenCL v1.2 s6.5.3: a string literal is in the constant address space. + if (getLangOpts().OpenCL) + return LangAS::opencl_constant; + if (getLangOpts().SYCLIsDevice) + return LangAS::sycl_global; + if (auto AS = getTarget().getConstantAddressSpace()) + return AS.value(); + return LangAS::Default; +} + +static mlir::cir::GlobalOp +generateStringLiteral(mlir::Location loc, mlir::TypedAttr C, + mlir::cir::GlobalLinkageKind LT, CIRGenModule &CGM, + StringRef GlobalName, CharUnits Alignment) { + unsigned AddrSpace = CGM.getASTContext().getTargetAddressSpace( + CGM.getGlobalConstantAddressSpace()); + assert((AddrSpace == 0 && + !cir::UnimplementedFeature::addressSpaceInGlobalVar()) && + "NYI"); + + // Create a global variable for this string + // FIXME(cir): check for insertion point in module level. + auto GV = CIRGenModule::createGlobalOp(CGM, loc, GlobalName, C.getType(), + !CGM.getLangOpts().WritableStrings); + + // Set up extra information and add to the module + GV.setAlignmentAttr(CGM.getSize(Alignment)); + GV.setLinkageAttr( + mlir::cir::GlobalLinkageKindAttr::get(CGM.getBuilder().getContext(), LT)); + CIRGenModule::setInitializer(GV, C); + + // TODO(cir) + assert(!cir::UnimplementedFeature::threadLocal() && "NYI"); + assert(!cir::UnimplementedFeature::unnamedAddr() && "NYI"); + assert(!mlir::cir::isWeakForLinker(LT) && "NYI"); + assert(!cir::UnimplementedFeature::setDSOLocal() && "NYI"); + return GV; +} + +/// Return a pointer to a constant array for the given string literal. +mlir::cir::GlobalViewAttr +CIRGenModule::getAddrOfConstantStringFromLiteral(const StringLiteral *S, + StringRef Name) { + CharUnits Alignment = astCtx.getAlignOfGlobalVarInChars(S->getType()); + + mlir::Attribute C = getConstantArrayFromStringLiteral(S); + + mlir::cir::GlobalOp GV; + if (!getLangOpts().WritableStrings && ConstantStringMap.count(C)) { + GV = ConstantStringMap[C]; + // The bigger alignment always wins. + if (!GV.getAlignment() || + uint64_t(Alignment.getQuantity()) > *GV.getAlignment()) + GV.setAlignmentAttr(getSize(Alignment)); + } else { + SmallString<256> StringNameBuffer = Name; + llvm::raw_svector_ostream Out(StringNameBuffer); + if (StringLiteralCnt) + Out << StringLiteralCnt; + Name = Out.str(); + StringLiteralCnt++; + + SmallString<256> MangledNameBuffer; + StringRef GlobalVariableName; + auto LT = mlir::cir::GlobalLinkageKind::ExternalLinkage; + + // Mangle the string literal if that's how the ABI merges duplicate strings. + // Don't do it if they are writable, since we don't want writes in one TU to + // affect strings in another. + if (getCXXABI().getMangleContext().shouldMangleStringLiteral(S) && + !getLangOpts().WritableStrings) { + assert(0 && "not implemented"); + } else { + LT = mlir::cir::GlobalLinkageKind::InternalLinkage; + GlobalVariableName = Name; + } + + auto loc = getLoc(S->getSourceRange()); + auto typedC = llvm::dyn_cast(C); + if (!typedC) + llvm_unreachable("this should never be untyped at this point"); + GV = generateStringLiteral(loc, typedC, LT, *this, GlobalVariableName, + Alignment); + ConstantStringMap[C] = GV; + + assert(!cir::UnimplementedFeature::reportGlobalToASan() && "NYI"); + } + + auto ArrayTy = GV.getSymType().dyn_cast(); + assert(ArrayTy && "String literal must be array"); + auto PtrTy = + mlir::cir::PointerType::get(builder.getContext(), ArrayTy.getEltType()); + + return builder.getGlobalViewAttr(PtrTy, GV); +} + +void CIRGenModule::buildDeclContext(const DeclContext *DC) { + for (auto *I : DC->decls()) { + // Unlike other DeclContexts, the contents of an ObjCImplDecl at TU scope + // are themselves considered "top-level", so EmitTopLevelDecl on an + // ObjCImplDecl does not recursively visit them. We need to do that in + // case they're nested inside another construct (LinkageSpecDecl / + // ExportDecl) that does stop them from being considered "top-level". + if (auto *OID = dyn_cast(I)) + llvm_unreachable("NYI"); + + buildTopLevelDecl(I); + } +} + +void CIRGenModule::buildLinkageSpec(const LinkageSpecDecl *LSD) { + if (LSD->getLanguage() != LinkageSpecDecl::lang_c && + LSD->getLanguage() != LinkageSpecDecl::lang_cxx) { + llvm_unreachable("unsupported linkage spec"); + return; + } + buildDeclContext(LSD); +} + +// Emit code for a single top level declaration. +void CIRGenModule::buildTopLevelDecl(Decl *decl) { + // Ignore dependent declarations + if (decl->isTemplated()) + return; + + // Consteval function shouldn't be emitted. + if (auto *FD = dyn_cast(decl)) + if (FD->isConsteval()) + return; + + switch (decl->getKind()) { + default: + llvm::errs() << "buildTopLevelDecl codegen for decl kind '" + << decl->getDeclKindName() << "' not implemented\n"; + assert(false && "Not yet implemented"); + + case Decl::TranslationUnit: { + // This path is CIR only - CIRGen handles TUDecls because + // of clang-tidy checks, that operate on TU granularity. + TranslationUnitDecl *TU = cast(decl); + for (DeclContext::decl_iterator D = TU->decls_begin(), + DEnd = TU->decls_end(); + D != DEnd; ++D) + buildTopLevelDecl(*D); + return; + } + case Decl::Var: + case Decl::Decomposition: + case Decl::VarTemplateSpecialization: + buildGlobal(cast(decl)); + assert(!isa(decl) && "not implemented"); + // if (auto *DD = dyn_cast(decl)) + // for (auto *B : DD->bindings()) + // if (auto *HD = B->getHoldingVar()) + // EmitGlobal(HD); + break; + + case Decl::CXXConversion: + case Decl::CXXMethod: + case Decl::Function: + buildGlobal(cast(decl)); + assert(!codeGenOpts.CoverageMapping && "Coverage Mapping NYI"); + break; + // C++ Decls + case Decl::Namespace: + buildDeclContext(cast(decl)); + break; + case Decl::ClassTemplateSpecialization: { + // const auto *Spec = cast(decl); + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + } + [[fallthrough]]; + case Decl::CXXRecord: { + CXXRecordDecl *crd = cast(decl); + // TODO: Handle debug info as CodeGenModule.cpp does + for (auto *childDecl : crd->decls()) + if (isa(childDecl) || isa(childDecl)) + buildTopLevelDecl(childDecl); + break; + } + // No code generation needed. + case Decl::UsingShadow: + case Decl::ClassTemplate: + case Decl::VarTemplate: + case Decl::Concept: + case Decl::VarTemplatePartialSpecialization: + case Decl::FunctionTemplate: + case Decl::TypeAliasTemplate: + case Decl::Block: + case Decl::Empty: + case Decl::Binding: + break; + case Decl::Using: // using X; [C++] + case Decl::UsingEnum: // using enum X; [C++] + case Decl::NamespaceAlias: + case Decl::UsingDirective: // using namespace X; [C++] + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + break; + case Decl::CXXConstructor: + getCXXABI().buildCXXConstructors(cast(decl)); + break; + case Decl::CXXDestructor: + getCXXABI().buildCXXDestructors(cast(decl)); + break; + + case Decl::StaticAssert: + // Nothing to do. + break; + + case Decl::LinkageSpec: + buildLinkageSpec(cast(decl)); + break; + + case Decl::Typedef: + case Decl::TypeAlias: // using foo = bar; [C++11] + case Decl::Record: + case Decl::Enum: + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); + break; + } +} + +static bool shouldBeInCOMDAT(CIRGenModule &CGM, const Decl &D) { + if (!CGM.supportsCOMDAT()) + return false; + + if (D.hasAttr()) + return true; + + GVALinkage Linkage; + if (auto *VD = dyn_cast(&D)) + Linkage = CGM.getASTContext().GetGVALinkageForVariable(VD); + else + Linkage = + CGM.getASTContext().GetGVALinkageForFunction(cast(&D)); + + switch (Linkage) { + case clang::GVA_Internal: + case clang::GVA_AvailableExternally: + case clang::GVA_StrongExternal: + return false; + case clang::GVA_DiscardableODR: + case clang::GVA_StrongODR: + return true; + } + llvm_unreachable("No such linkage"); +} + +// TODO(cir): this could be a common method between LLVM codegen. +static bool isVarDeclStrongDefinition(const ASTContext &Context, + CIRGenModule &CGM, const VarDecl *D, + bool NoCommon) { + // Don't give variables common linkage if -fno-common was specified unless it + // was overridden by a NoCommon attribute. + if ((NoCommon || D->hasAttr()) && !D->hasAttr()) + return true; + + // C11 6.9.2/2: + // A declaration of an identifier for an object that has file scope without + // an initializer, and without a storage-class specifier or with the + // storage-class specifier static, constitutes a tentative definition. + if (D->getInit() || D->hasExternalStorage()) + return true; + + // A variable cannot be both common and exist in a section. + if (D->hasAttr()) + return true; + + // A variable cannot be both common and exist in a section. + // We don't try to determine which is the right section in the front-end. + // If no specialized section name is applicable, it will resort to default. + if (D->hasAttr() || + D->hasAttr() || + D->hasAttr() || + D->hasAttr()) + return true; + + // Thread local vars aren't considered common linkage. + if (D->getTLSKind()) + return true; + + // Tentative definitions marked with WeakImportAttr are true definitions. + if (D->hasAttr()) + return true; + + // A variable cannot be both common and exist in a comdat. + if (shouldBeInCOMDAT(CGM, *D)) + return true; + + // Declarations with a required alignment do not have common linkage in MSVC + // mode. + if (Context.getTargetInfo().getCXXABI().isMicrosoft()) { + if (D->hasAttr()) + return true; + QualType VarType = D->getType(); + if (Context.isAlignmentRequired(VarType)) + return true; + + if (const auto *RT = VarType->getAs()) { + const RecordDecl *RD = RT->getDecl(); + for (const FieldDecl *FD : RD->fields()) { + if (FD->isBitField()) + continue; + if (FD->hasAttr()) + return true; + if (Context.isAlignmentRequired(FD->getType())) + return true; + } + } + } + + // Microsoft's link.exe doesn't support alignments greater than 32 bytes for + // common symbols, so symbols with greater alignment requirements cannot be + // common. + // Other COFF linkers (ld.bfd and LLD) support arbitrary power-of-two + // alignments for common symbols via the aligncomm directive, so this + // restriction only applies to MSVC environments. + if (Context.getTargetInfo().getTriple().isKnownWindowsMSVCEnvironment() && + Context.getTypeAlignIfKnown(D->getType()) > + Context.toBits(CharUnits::fromQuantity(32))) + return true; + + return false; +} + +void CIRGenModule::setInitializer(mlir::cir::GlobalOp &global, + mlir::Attribute value) { + // Recompute visibility when updating initializer. + global.setInitialValueAttr(value); + mlir::SymbolTable::setSymbolVisibility( + global, CIRGenModule::getMLIRVisibility(global)); +} + +mlir::SymbolTable::Visibility +CIRGenModule::getMLIRVisibility(mlir::cir::GlobalOp op) { + // MLIR doesn't accept public symbols declarations (only + // definitions). + if (op.isDeclaration()) + return mlir::SymbolTable::Visibility::Private; + return getMLIRVisibilityFromCIRLinkage(op.getLinkage()); +} + +mlir::SymbolTable::Visibility CIRGenModule::getMLIRVisibilityFromCIRLinkage( + mlir::cir::GlobalLinkageKind GLK) { + switch (GLK) { + case mlir::cir::GlobalLinkageKind::InternalLinkage: + case mlir::cir::GlobalLinkageKind::PrivateLinkage: + return mlir::SymbolTable::Visibility::Private; + case mlir::cir::GlobalLinkageKind::ExternalLinkage: + case mlir::cir::GlobalLinkageKind::ExternalWeakLinkage: + case mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage: + case mlir::cir::GlobalLinkageKind::AvailableExternallyLinkage: + return mlir::SymbolTable::Visibility::Public; + default: { + llvm::errs() << "visibility not implemented for '" + << stringifyGlobalLinkageKind(GLK) << "'\n"; + assert(0 && "not implemented"); + } + } + llvm_unreachable("linkage should be handled above!"); +} + +mlir::cir::GlobalLinkageKind CIRGenModule::getCIRLinkageForDeclarator( + const DeclaratorDecl *D, GVALinkage Linkage, bool IsConstantVariable) { + if (Linkage == GVA_Internal) + return mlir::cir::GlobalLinkageKind::InternalLinkage; + + if (D->hasAttr()) { + if (IsConstantVariable) + return mlir::cir::GlobalLinkageKind::WeakODRLinkage; + else + return mlir::cir::GlobalLinkageKind::WeakAnyLinkage; + } + + if (const auto *FD = D->getAsFunction()) + if (FD->isMultiVersion() && Linkage == GVA_AvailableExternally) + return mlir::cir::GlobalLinkageKind::LinkOnceAnyLinkage; + + // We are guaranteed to have a strong definition somewhere else, + // so we can use available_externally linkage. + if (Linkage == GVA_AvailableExternally) + return mlir::cir::GlobalLinkageKind::AvailableExternallyLinkage; + + // Note that Apple's kernel linker doesn't support symbol + // coalescing, so we need to avoid linkonce and weak linkages there. + // Normally, this means we just map to internal, but for explicit + // instantiations we'll map to external. + + // In C++, the compiler has to emit a definition in every translation unit + // that references the function. We should use linkonce_odr because + // a) if all references in this translation unit are optimized away, we + // don't need to codegen it. b) if the function persists, it needs to be + // merged with other definitions. c) C++ has the ODR, so we know the + // definition is dependable. + if (Linkage == GVA_DiscardableODR) + return !astCtx.getLangOpts().AppleKext + ? mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage + : mlir::cir::GlobalLinkageKind::InternalLinkage; + + // An explicit instantiation of a template has weak linkage, since + // explicit instantiations can occur in multiple translation units + // and must all be equivalent. However, we are not allowed to + // throw away these explicit instantiations. + // + // CUDA/HIP: For -fno-gpu-rdc case, device code is limited to one TU, + // so say that CUDA templates are either external (for kernels) or internal. + // This lets llvm perform aggressive inter-procedural optimizations. For + // -fgpu-rdc case, device function calls across multiple TU's are allowed, + // therefore we need to follow the normal linkage paradigm. + if (Linkage == GVA_StrongODR) { + if (getLangOpts().AppleKext) + return mlir::cir::GlobalLinkageKind::ExternalLinkage; + if (getLangOpts().CUDA && getLangOpts().CUDAIsDevice && + !getLangOpts().GPURelocatableDeviceCode) + return D->hasAttr() + ? mlir::cir::GlobalLinkageKind::ExternalLinkage + : mlir::cir::GlobalLinkageKind::InternalLinkage; + return mlir::cir::GlobalLinkageKind::WeakODRLinkage; + } + + // C++ doesn't have tentative definitions and thus cannot have common + // linkage. + if (!getLangOpts().CPlusPlus && isa(D) && + !isVarDeclStrongDefinition(astCtx, *this, cast(D), + getCodeGenOpts().NoCommon)) + return mlir::cir::GlobalLinkageKind::CommonLinkage; + + // selectany symbols are externally visible, so use weak instead of + // linkonce. MSVC optimizes away references to const selectany globals, so + // all definitions should be the same and ODR linkage should be used. + // http://msdn.microsoft.com/en-us/library/5tkz6s71.aspx + if (D->hasAttr()) + return mlir::cir::GlobalLinkageKind::WeakODRLinkage; + + // Otherwise, we have strong external linkage. + assert(Linkage == GVA_StrongExternal); + return mlir::cir::GlobalLinkageKind::ExternalLinkage; +} + +/// This function is called when we implement a function with no prototype, e.g. +/// "int foo() {}". If there are existing call uses of the old function in the +/// module, this adjusts them to call the new function directly. +/// +/// This is not just a cleanup: the always_inline pass requires direct calls to +/// functions to be able to inline them. If there is a bitcast in the way, it +/// won't inline them. Instcombine normally deletes these calls, but it isn't +/// run at -O0. +void CIRGenModule::ReplaceUsesOfNonProtoTypeWithRealFunction( + mlir::Operation *Old, mlir::cir::FuncOp NewFn) { + + // If we're redefining a global as a function, don't transform it. + auto OldFn = dyn_cast(Old); + if (!OldFn) + return; + + // TODO(cir): this RAUW ignores the features below. + assert(!UnimplementedFeature::exceptions() && "Call vs Invoke NYI"); + assert(!UnimplementedFeature::parameterAttributes()); + assert(!UnimplementedFeature::operandBundles()); + assert(OldFn->getAttrs().size() > 1 && "Attribute forwarding NYI"); + + // Mark new function as originated from a no-proto declaration. + NewFn.setNoProtoAttr(OldFn.getNoProtoAttr()); + + // Iterate through all calls of the no-proto function. + auto Calls = OldFn.getSymbolUses(OldFn->getParentOp()); + for (auto Call : Calls.value()) { + mlir::OpBuilder::InsertionGuard guard(builder); + + // Fetch no-proto call to be replaced. + auto noProtoCallOp = dyn_cast(Call.getUser()); + assert(noProtoCallOp && "unexpected use of no-proto function"); + builder.setInsertionPoint(noProtoCallOp); + + // Patch call type with the real function type. + auto realCallOp = builder.create( + noProtoCallOp.getLoc(), NewFn, noProtoCallOp.getOperands()); + + // Replace old no proto call with fixed call. + noProtoCallOp.replaceAllUsesWith(realCallOp); + noProtoCallOp.erase(); + } +} + +mlir::cir::GlobalLinkageKind +CIRGenModule::getCIRLinkageVarDefinition(const VarDecl *VD, bool IsConstant) { + assert(!IsConstant && "constant variables NYI"); + GVALinkage Linkage = astCtx.GetGVALinkageForVariable(VD); + return getCIRLinkageForDeclarator(VD, Linkage, IsConstant); +} + +mlir::cir::GlobalLinkageKind CIRGenModule::getFunctionLinkage(GlobalDecl GD) { + const auto *D = cast(GD.getDecl()); + + GVALinkage Linkage = astCtx.GetGVALinkageForFunction(D); + + if (const auto *Dtor = dyn_cast(D)) + return getCXXABI().getCXXDestructorLinkage(Linkage, Dtor, GD.getDtorType()); + + if (isa(D) && + cast(D)->isInheritingConstructor() && + astCtx.getTargetInfo().getCXXABI().isMicrosoft()) { + // Just like in LLVM codegen: + // Our approach to inheriting constructors is fundamentally different from + // that used by the MS ABI, so keep our inheriting constructor thunks + // internal rather than trying to pick an unambiguous mangling for them. + return mlir::cir::GlobalLinkageKind::InternalLinkage; + } + + return getCIRLinkageForDeclarator(D, Linkage, /*IsConstantVariable=*/false); +} + +mlir::Type CIRGenModule::getCIRType(const QualType &type) { + return genTypes.ConvertType(type); +} + +bool CIRGenModule::verifyModule() { + // Verify the module after we have finished constructing it, this will + // check the structural properties of the IR and invoke any specific + // verifiers we have on the CIR operations. + return mlir::verify(theModule).succeeded(); +} + +std::pair +CIRGenModule::getAddrAndTypeOfCXXStructor(GlobalDecl GD, + const CIRGenFunctionInfo *FnInfo, + mlir::cir::FuncType FnType, + bool Dontdefer, + ForDefinition_t IsForDefinition) { + auto *MD = cast(GD.getDecl()); + + if (isa(MD)) { + // Always alias equivalent complete destructors to base destructors in the + // MS ABI. + if (getTarget().getCXXABI().isMicrosoft() && + GD.getDtorType() == Dtor_Complete && + MD->getParent()->getNumVBases() == 0) + llvm_unreachable("NYI"); + } + + if (!FnType) { + if (!FnInfo) + FnInfo = &getTypes().arrangeCXXStructorDeclaration(GD); + FnType = getTypes().GetFunctionType(*FnInfo); + } + + auto Fn = GetOrCreateCIRFunction(getMangledName(GD), FnType, GD, + /*ForVtable=*/false, Dontdefer, + /*IsThunk=*/false, IsForDefinition); + + return {FnType, Fn}; +} + +mlir::cir::FuncOp +CIRGenModule::GetAddrOfFunction(clang::GlobalDecl GD, mlir::Type Ty, + bool ForVTable, bool DontDefer, + ForDefinition_t IsForDefinition) { + assert(!cast(GD.getDecl())->isConsteval() && + "consteval function should never be emitted"); + + if (!Ty) { + const auto *FD = cast(GD.getDecl()); + Ty = getTypes().ConvertType(FD->getType()); + } + + // Devirtualized destructor calls may come through here instead of via + // getAddrOfCXXStructor. Make sure we use the MS ABI base destructor instead + // of the complete destructor when necessary. + if (const auto *DD = dyn_cast(GD.getDecl())) { + if (getTarget().getCXXABI().isMicrosoft() && + GD.getDtorType() == Dtor_Complete && + DD->getParent()->getNumVBases() == 0) + llvm_unreachable("NYI"); + } + + StringRef MangledName = getMangledName(GD); + auto F = GetOrCreateCIRFunction(MangledName, Ty, GD, ForVTable, DontDefer, + /*IsThunk=*/false, IsForDefinition); + + assert(!langOpts.CUDA && "NYI"); + + return F; +} + +// Returns true if GD is a function decl with internal linkage and needs a +// unique suffix after the mangled name. +static bool isUniqueInternalLinkageDecl(GlobalDecl GD, CIRGenModule &CGM) { + assert(CGM.getModuleNameHash().empty() && + "Unique internal linkage names NYI"); + + return false; +} + +static std::string getMangledNameImpl(CIRGenModule &CGM, GlobalDecl GD, + const NamedDecl *ND, + bool OmitMultiVersionMangling = false) { + assert(!OmitMultiVersionMangling && "NYI"); + + SmallString<256> Buffer; + + llvm::raw_svector_ostream Out(Buffer); + MangleContext &MC = CGM.getCXXABI().getMangleContext(); + + assert(CGM.getModuleNameHash().empty() && "NYI"); + auto ShouldMangle = MC.shouldMangleDeclName(ND); + + if (ShouldMangle) { + MC.mangleName(GD.getWithDecl(ND), Out); + } else { + auto *II = ND->getIdentifier(); + assert(II && "Attempt to mangle unnamed decl."); + + const auto *FD = dyn_cast(ND); + + if (FD && + FD->getType()->castAs()->getCallConv() == CC_X86RegCall) { + assert(0 && "NYI"); + } else if (FD && FD->hasAttr() && + GD.getKernelReferenceKind() == KernelReferenceKind::Stub) { + assert(0 && "NYI"); + } else { + Out << II->getName(); + } + } + + // Check if the module name hash should be appended for internal linkage + // symbols. This should come before multi-version target suffixes are + // appendded. This is to keep the name and module hash suffix of the internal + // linkage function together. The unique suffix should only be added when name + // mangling is done to make sure that the final name can be properly + // demangled. For example, for C functions without prototypes, name mangling + // is not done and the unique suffix should not be appended then. + assert(!isUniqueInternalLinkageDecl(GD, CGM) && "NYI"); + + if (const auto *FD = dyn_cast(ND)) { + assert(!FD->isMultiVersion() && "NYI"); + } + assert(!CGM.getLangOpts().GPURelocatableDeviceCode && "NYI"); + + return std::string(Out.str()); +} + +StringRef CIRGenModule::getMangledName(GlobalDecl GD) { + auto CanonicalGD = GD.getCanonicalDecl(); + + // Some ABIs don't have constructor variants. Make sure that base and complete + // constructors get mangled the same. + if (const auto *CD = dyn_cast(CanonicalGD.getDecl())) { + if (!getTarget().getCXXABI().hasConstructorVariants()) { + assert(false && "NYI"); + } + } + + assert(!langOpts.CUDAIsDevice && "NYI"); + + // Keep the first result in the case of a mangling collision. + const auto *ND = cast(GD.getDecl()); + std::string MangledName = getMangledNameImpl(*this, GD, ND); + + auto Result = Manglings.insert(std::make_pair(MangledName, GD)); + return MangledDeclNames[CanonicalGD] = Result.first->first(); +} + +void CIRGenModule::buildTentativeDefinition(const VarDecl *D) { + assert(!D->getInit() && "Cannot emit definite definitions here!"); + + StringRef MangledName = getMangledName(D); + auto *GV = getGlobalValue(MangledName); + + // TODO(cir): can a tentative definition come from something other than a + // global op? If not, the assertion below is wrong and should be removed. If + // so, getGlobalValue might be better of returining a global value interface + // that alows use to manage different globals value types transparently. + if (GV) + assert(isa(GV) && + "tentative definition can only be built from a cir.global_op"); + + // We already have a definition, not declaration, with the same mangled name. + // Emitting of declaration is not required (and actually overwrites emitted + // definition). + if (GV && !dyn_cast(GV).isDeclaration()) + return; + + // If we have not seen a reference to this variable yet, place it into the + // deferred declarations table to be emitted if needed later. + if (!MustBeEmitted(D) && !GV) { + DeferredDecls[MangledName] = D; + return; + } + + // The tentative definition is the only definition. + buildGlobalVarDefinition(D); +} + +void CIRGenModule::setGlobalVisibility(mlir::Operation *GV, + const NamedDecl *D) const { + assert(!UnimplementedFeature::setGlobalVisibility()); +} + +void CIRGenModule::setDSOLocal(mlir::Operation *Op) const { + assert(!UnimplementedFeature::setDSOLocal()); +} + +void CIRGenModule::setGVProperties(mlir::Operation *Op, + const NamedDecl *D) const { + assert(!UnimplementedFeature::setDLLImportDLLExport()); + setGVPropertiesAux(Op, D); +} + +void CIRGenModule::setGVPropertiesAux(mlir::Operation *Op, + const NamedDecl *D) const { + setGlobalVisibility(Op, D); + setDSOLocal(Op); + assert(!UnimplementedFeature::setPartition()); +} + +bool CIRGenModule::lookupRepresentativeDecl(StringRef MangledName, + GlobalDecl &Result) const { + auto Res = Manglings.find(MangledName); + if (Res == Manglings.end()) + return false; + Result = Res->getValue(); + return true; +} + +mlir::cir::FuncOp +CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name, + mlir::cir::FuncType Ty, + const clang::FunctionDecl *FD) { + // At the point we need to create the function, the insertion point + // could be anywhere (e.g. callsite). Do not rely on whatever it might + // be, properly save, find the appropriate place and restore. + FuncOp f; + { + mlir::OpBuilder::InsertionGuard guard(builder); + + // Some global emissions are triggered while emitting a function, e.g. + // void s() { x.method() } + // + // Be sure to insert a new function before a current one. + auto *curCGF = getCurrCIRGenFun(); + if (curCGF) + builder.setInsertionPoint(curCGF->CurFn); + + f = builder.create(loc, name, Ty); + + if (FD) + f.setAstAttr(makeFuncDeclAttr(FD, builder.getContext())); + + if (FD && !FD->hasPrototype()) + f.setNoProtoAttr(builder.getUnitAttr()); + + assert(f.isDeclaration() && "expected empty body"); + + // A declaration gets private visibility by default, but external linkage + // as the default linkage. + f.setLinkageAttr(mlir::cir::GlobalLinkageKindAttr::get( + builder.getContext(), mlir::cir::GlobalLinkageKind::ExternalLinkage)); + mlir::SymbolTable::setSymbolVisibility( + f, mlir::SymbolTable::Visibility::Private); + + setExtraAttributesForFunc(f, FD); + + if (!curCGF) + theModule.push_back(f); + } + return f; +} + +bool isDefaultedMethod(const clang::FunctionDecl *FD) { + if (FD->isDefaulted() && isa(FD) && + (cast(FD)->isCopyAssignmentOperator() || + cast(FD)->isMoveAssignmentOperator())) + return true; + return false; +} + +mlir::Location CIRGenModule::getLocForFunction(const clang::FunctionDecl *FD) { + assert(FD && "Not sure which location to use yet"); + bool invalidLoc = (FD->getSourceRange().getBegin().isInvalid() || + FD->getSourceRange().getEnd().isInvalid()); + if (!invalidLoc) + return getLoc(FD->getSourceRange()); + + // Use the module location + return theModule->getLoc(); +} + +void CIRGenModule::setExtraAttributesForFunc(FuncOp f, + const clang::FunctionDecl *FD) { + mlir::NamedAttrList attrs; + + if (!FD) { + // If we don't have a declaration to control inlining, the function isn't + // explicitly marked as alwaysinline for semantic reasons, and inlining is + // disabled, mark the function as noinline. + if (codeGenOpts.getInlining() == CodeGenOptions::OnlyAlwaysInlining) { + auto attr = mlir::cir::InlineAttr::get( + builder.getContext(), mlir::cir::InlineKind::AlwaysInline); + attrs.set(attr.getMnemonic(), attr); + } + } else if (FD->hasAttr()) { + // Add noinline if the function isn't always_inline. + auto attr = mlir::cir::InlineAttr::get(builder.getContext(), + mlir::cir::InlineKind::NoInline); + attrs.set(attr.getMnemonic(), attr); + } else if (FD->hasAttr()) { + // (noinline wins over always_inline, and we can't specify both in IR) + auto attr = mlir::cir::InlineAttr::get(builder.getContext(), + mlir::cir::InlineKind::AlwaysInline); + attrs.set(attr.getMnemonic(), attr); + } else if (codeGenOpts.getInlining() == CodeGenOptions::OnlyAlwaysInlining) { + // If we're not inlining, then force everything that isn't always_inline + // to carry an explicit noinline attribute. + auto attr = mlir::cir::InlineAttr::get(builder.getContext(), + mlir::cir::InlineKind::NoInline); + attrs.set(attr.getMnemonic(), attr); + } else { + // Otherwise, propagate the inline hint attribute and potentially use its + // absence to mark things as noinline. + // Search function and template pattern redeclarations for inline. + auto CheckForInline = [](const FunctionDecl *FD) { + auto CheckRedeclForInline = [](const FunctionDecl *Redecl) { + return Redecl->isInlineSpecified(); + }; + if (any_of(FD->redecls(), CheckRedeclForInline)) + return true; + const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern(); + if (!Pattern) + return false; + return any_of(Pattern->redecls(), CheckRedeclForInline); + }; + if (CheckForInline(FD)) { + auto attr = mlir::cir::InlineAttr::get(builder.getContext(), + mlir::cir::InlineKind::InlineHint); + attrs.set(attr.getMnemonic(), attr); + } else if (codeGenOpts.getInlining() == CodeGenOptions::OnlyHintInlining) { + auto attr = mlir::cir::InlineAttr::get(builder.getContext(), + mlir::cir::InlineKind::NoInline); + attrs.set(attr.getMnemonic(), attr); + } + } + + // Track whether we need to add the optnone attribute, + // starting with the default for this optimization level. + bool ShouldAddOptNone = + !codeGenOpts.DisableO0ImplyOptNone && codeGenOpts.OptimizationLevel == 0; + if (FD) { + ShouldAddOptNone &= !FD->hasAttr(); + ShouldAddOptNone &= !FD->hasAttr(); + ShouldAddOptNone |= FD->hasAttr(); + } + + if (ShouldAddOptNone) { + auto optNoneAttr = mlir::cir::OptNoneAttr::get(builder.getContext()); + attrs.set(optNoneAttr.getMnemonic(), optNoneAttr); + + // OptimizeNone implies noinline; we should not be inlining such functions. + auto noInlineAttr = mlir::cir::InlineAttr::get( + builder.getContext(), mlir::cir::InlineKind::NoInline); + attrs.set(noInlineAttr.getMnemonic(), noInlineAttr); + } + + f.setExtraAttrsAttr(mlir::cir::ExtraFuncAttributesAttr::get( + builder.getContext(), attrs.getDictionary(builder.getContext()))); +} + +/// If the specified mangled name is not in the module, +/// create and return a CIR Function with the specified type. If there is +/// something in the module with the specified name, return it potentially +/// bitcasted to the right type. +/// +/// If D is non-null, it specifies a decl that corresponded to this. This is +/// used to set the attributes on the function when it is first created. +mlir::cir::FuncOp CIRGenModule::GetOrCreateCIRFunction( + StringRef MangledName, mlir::Type Ty, GlobalDecl GD, bool ForVTable, + bool DontDefer, bool IsThunk, ForDefinition_t IsForDefinition) { + assert(!IsThunk && "NYI"); + + const auto *D = GD.getDecl(); + + // Any attempts to use a MultiVersion function should result in retrieving the + // iFunc instead. Name mangling will handle the rest of the changes. + if (const auto *FD = cast_or_null(D)) { + if (getLangOpts().OpenMPIsTargetDevice) + llvm_unreachable("open MP NYI"); + if (FD->isMultiVersion()) + llvm_unreachable("NYI"); + } + + // Lookup the entry, lazily creating it if necessary. + mlir::Operation *Entry = getGlobalValue(MangledName); + if (Entry) { + assert(isa(Entry) && + "not implemented, only supports FuncOp for now"); + + if (WeakRefReferences.erase(Entry)) { + llvm_unreachable("NYI"); + } + + // Handle dropped DLL attributes. + if (D && !D->hasAttr() && !D->hasAttr()) { + // TODO(CIR): Entry->setDLLStorageClass + setDSOLocal(Entry); + } + + // If there are two attempts to define the same mangled name, issue an + // error. + auto Fn = cast(Entry); + if (IsForDefinition && Fn && !Fn.isDeclaration()) { + GlobalDecl OtherGD; + // CHeck that GD is not yet in DiagnosedConflictingDefinitions is required + // to make sure that we issue and error only once. + if (lookupRepresentativeDecl(MangledName, OtherGD) && + (GD.getCanonicalDecl().getDecl()) && + DiagnosedConflictingDefinitions.insert(GD).second) { + getDiags().Report(D->getLocation(), diag::err_duplicate_mangled_name) + << MangledName; + getDiags().Report(OtherGD.getDecl()->getLocation(), + diag::note_previous_definition); + } + } + + if (Fn && Fn.getFunctionType() == Ty) { + return Fn; + } + + if (!IsForDefinition) { + return Fn; + } + + // TODO: clang checks here if this is a llvm::GlobalAlias... how will we + // support this? + } + + // This function doesn't have a complete type (for example, the return type is + // an incomplete struct). Use a fake type instead, and make sure not to try to + // set attributes. + bool IsIncompleteFunction = false; + + mlir::cir::FuncType FTy; + if (Ty.isa()) { + FTy = Ty.cast(); + } else { + assert(false && "NYI"); + // FTy = mlir::FunctionType::get(VoidTy, false); + IsIncompleteFunction = true; + } + + auto *FD = llvm::cast(D); + assert(FD && "Only FunctionDecl supported so far."); + + // TODO: CodeGen includeds the linkage (ExternalLinkage) and only passes the + // mangledname if Entry is nullptr + auto F = createCIRFunction(getLocForFunction(FD), MangledName, FTy, FD); + + // If we already created a function with the same mangled name (but different + // type) before, take its name and add it to the list of functions to be + // replaced with F at the end of CodeGen. + // + // This happens if there is a prototype for a function (e.g. "int f()") and + // then a definition of a different type (e.g. "int f(int x)"). + if (Entry) { + + // Fetch a generic symbol-defining operation and its uses. + auto SymbolOp = dyn_cast(Entry); + assert(SymbolOp && "Expected a symbol-defining operation"); + + // TODO(cir): When can this symbol be something other than a function? + assert(isa(Entry) && "NYI"); + + // This might be an implementation of a function without a prototype, in + // which case, try to do special replacement of calls which match the new + // prototype. The really key thing here is that we also potentially drop + // arguments from the call site so as to make a direct call, which makes the + // inliner happier and suppresses a number of optimizer warnings (!) about + // dropping arguments. + if (SymbolOp.getSymbolUses(SymbolOp->getParentOp())) { + ReplaceUsesOfNonProtoTypeWithRealFunction(Entry, F); + } + + // Obliterate no-proto declaration. + Entry->erase(); + } + + // TODO: This might not be valid, seems the uniqueing system doesn't make + // sense for MLIR + // assert(F->getName().getStringRef() == MangledName && "name was uniqued!"); + + if (D) + ; // TODO: set function attributes from the declaration + + // TODO: set function attributes from the missing attributes param + + // TODO: Handle extra attributes + + if (!DontDefer) { + // All MSVC dtors other than the base dtor are linkonce_odr and delegate to + // each other bottoming out wiht the base dtor. Therefore we emit non-base + // dtors on usage, even if there is no dtor definition in the TU. + if (isa_and_nonnull(D) && + getCXXABI().useThunkForDtorVariant(cast(D), + GD.getDtorType())) { + llvm_unreachable("NYI"); // addDeferredDeclToEmit(GD); + } + + // This is the first use or definition of a mangled name. If there is a + // deferred decl with this name, remember that we need to emit it at the end + // of the file. + auto DDI = DeferredDecls.find(MangledName); + if (DDI != DeferredDecls.end()) { + // Move the potentially referenced deferred decl to the + // DeferredDeclsToEmit list, and remove it from DeferredDecls (since we + // don't need it anymore). + addDeferredDeclToEmit(DDI->second); + DeferredDecls.erase(DDI); + + // Otherwise, there are cases we have to worry about where we're using a + // declaration for which we must emit a definition but where we might not + // find a top-level definition. + // - member functions defined inline in their classes + // - friend functions defined inline in some class + // - special member functions with implicit definitions + // If we ever change our AST traversal to walk into class methods, this + // will be unnecessary. + // + // We also don't emit a definition for a function if it's going to be an + // entry in a vtable, unless it's already marked as used. + } else if (getLangOpts().CPlusPlus && D) { + // Look for a declaration that's lexically in a record. + for (const auto *FD = cast(D)->getMostRecentDecl(); FD; + FD = FD->getPreviousDecl()) { + if (isa(FD->getLexicalDeclContext())) { + if (FD->doesThisDeclarationHaveABody()) { + if (isDefaultedMethod(FD)) + addDefaultMethodsToEmit(GD.getWithDecl(FD)); + else + addDeferredDeclToEmit(GD.getWithDecl(FD)); + break; + } + } + } + } + } + + if (!IsIncompleteFunction) { + assert(F.getFunctionType() == Ty); + return F; + } + + // TODO(cir): Might need bitcast to different address space. + assert(!UnimplementedFeature::addressSpace()); + return F; +} + +mlir::Location CIRGenModule::getLoc(SourceLocation SLoc) { + assert(SLoc.isValid() && "expected valid source location"); + const SourceManager &SM = astCtx.getSourceManager(); + PresumedLoc PLoc = SM.getPresumedLoc(SLoc); + StringRef Filename = PLoc.getFilename(); + return mlir::FileLineColLoc::get(builder.getStringAttr(Filename), + PLoc.getLine(), PLoc.getColumn()); +} + +mlir::Location CIRGenModule::getLoc(SourceRange SLoc) { + assert(SLoc.isValid() && "expected valid source location"); + mlir::Location B = getLoc(SLoc.getBegin()); + mlir::Location E = getLoc(SLoc.getEnd()); + SmallVector locs = {B, E}; + mlir::Attribute metadata; + return mlir::FusedLoc::get(locs, metadata, builder.getContext()); +} + +mlir::Location CIRGenModule::getLoc(mlir::Location lhs, mlir::Location rhs) { + SmallVector locs = {lhs, rhs}; + mlir::Attribute metadata; + return mlir::FusedLoc::get(locs, metadata, builder.getContext()); +} + +void CIRGenModule::buildGlobalDecl(clang::GlobalDecl &D) { + // We should call GetAddrOfGlobal with IsForDefinition set to true in order + // to get a Value with exactly the type we need, not something that might + // have been created for another decl with the same mangled name but + // different type. + auto *Op = GetAddrOfGlobal(D, ForDefinition); + + // In case of different address spaces, we may still get a cast, even with + // IsForDefinition equal to true. Query mangled names table to get + // GlobalValue. + if (!Op) { + Op = getGlobalValue(getMangledName(D)); + } + + // In case of different address spaces, we may still get a cast, even with + // IsForDefinition equal to true. Query mangled names table to get + // GlobalValue. + if (!Op) + llvm_unreachable("Address spaces NYI"); + + // Make sure getGlobalValue returned non-null. + assert(Op); + + // Check to see if we've already emitted this. This is necessary for a + // couple of reasons: first, decls can end up in deferred-decls queue + // multiple times, and second, decls can end up with definitions in unusual + // ways (e.g. by an extern inline function acquiring a strong function + // redefinition). Just ignore those cases. + // TODO: Not sure what to map this to for MLIR + if (auto Fn = dyn_cast(Op)) + if (!Fn.isDeclaration()) + return; + + // TODO(cir): create a global value trait that allow us to uniformly handle + // global variables and functions. + if (auto Gv = dyn_cast(Op)) { + auto *result = + mlir::SymbolTable::lookupSymbolIn(getModule(), Gv.getNameAttr()); + if (auto globalOp = dyn_cast(result)) + if (!globalOp.isDeclaration()) + return; + } + + // If this is OpenMP, check if it is legal to emit this global normally. + if (getLangOpts().OpenMP) { + llvm_unreachable("NYI"); + } + + // Otherwise, emit the definition and move on to the next one. + buildGlobalDefinition(D, Op); +} + +void CIRGenModule::buildDeferred(unsigned recursionLimit) { + // Emit deferred declare target declarations + if (getLangOpts().OpenMP && !getLangOpts().OpenMPSimd) + llvm_unreachable("NYI"); + + // Emit code for any potentially referenced deferred decls. Since a previously + // unused static decl may become used during the generation of code for a + // static function, iterate until no changes are made. + + if (!DeferredVTables.empty()) { + buildDeferredVTables(); + + // Emitting a vtable doesn't directly cause more vtables to + // become deferred, although it can cause functions to be + // emitted that then need those vtables. + assert(DeferredVTables.empty()); + } + + // Emit CUDA/HIP static device variables referenced by host code only. Note we + // should not clear CUDADeviceVarODRUsedByHost since it is still needed for + // further handling. + if (getLangOpts().CUDA && getLangOpts().CUDAIsDevice) { + llvm_unreachable("NYI"); + } + + // Stop if we're out of both deferred vtables and deferred declarations. + if (DeferredDeclsToEmit.empty()) + return; + + // Grab the list of decls to emit. If buildGlobalDefinition schedules more + // work, it will not interfere with this. + std::vector CurDeclsToEmit; + CurDeclsToEmit.swap(DeferredDeclsToEmit); + if (recursionLimit == 0) + return; + recursionLimit--; + + for (auto &D : CurDeclsToEmit) { + if (getCodeGenOpts().ClangIRSkipFunctionsFromSystemHeaders) { + auto *decl = D.getDecl(); + assert(decl && "expected decl"); + if (astCtx.getSourceManager().isInSystemHeader(decl->getLocation())) + continue; + } + + buildGlobalDecl(D); + + // If we found out that we need to emit more decls, do that recursively. + // This has the advantage that the decls are emitted in a DFS and related + // ones are close together, which is convenient for testing. + if (!DeferredVTables.empty() || !DeferredDeclsToEmit.empty()) { + buildDeferred(recursionLimit); + assert(DeferredVTables.empty() && DeferredDeclsToEmit.empty()); + } + } +} + +void CIRGenModule::buildDefaultMethods() { + // Differently from DeferredDeclsToEmit, there's no recurrent use of + // DefaultMethodsToEmit, so use it directly for emission. + for (auto &D : DefaultMethodsToEmit) + buildGlobalDecl(D); +} + +mlir::IntegerAttr CIRGenModule::getSize(CharUnits size) { + // Note that mlir::IntegerType is used instead of mlir::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(builder.getContext(), 64), size.getQuantity()); +} + +mlir::Operation * +CIRGenModule::GetAddrOfGlobal(GlobalDecl GD, ForDefinition_t IsForDefinition) { + const Decl *D = GD.getDecl(); + + if (isa(D) || isa(D)) + return getAddrOfCXXStructor(GD, /*FnInfo=*/nullptr, /*FnType=*/nullptr, + /*DontDefer=*/false, IsForDefinition); + + if (isa(D)) { + auto FInfo = + &getTypes().arrangeCXXMethodDeclaration(cast(D)); + auto Ty = getTypes().GetFunctionType(*FInfo); + return GetAddrOfFunction(GD, Ty, /*ForVTable=*/false, /*DontDefer=*/false, + IsForDefinition); + } + + if (isa(D)) { + const CIRGenFunctionInfo &FI = getTypes().arrangeGlobalDeclaration(GD); + auto Ty = getTypes().GetFunctionType(FI); + return GetAddrOfFunction(GD, Ty, /*ForVTable=*/false, /*DontDefer=*/false, + IsForDefinition); + } + + return getAddrOfGlobalVar(cast(D), /*Ty=*/nullptr, IsForDefinition) + .getDefiningOp(); +} + +void CIRGenModule::Release() { + buildDeferred(getCodeGenOpts().ClangIRBuildDeferredThreshold); + // TODO: buildVTablesOpportunistically(); + // TODO: applyGlobalValReplacements(); + applyReplacements(); + // TODO: checkAliases(); + // TODO: buildMultiVersionFunctions(); + buildCXXGlobalInitFunc(); + // TODO: buildCXXGlobalCleanUpFunc(); + // TODO: registerGlobalDtorsWithAtExit(); + // TODO: buildCXXThreadLocalInitFunc(); + // TODO: ObjCRuntime + if (astCtx.getLangOpts().CUDA) { + llvm_unreachable("NYI"); + } + // TODO: OpenMPRuntime + // TODO: PGOReader + // TODO: buildCtorList(GlobalCtors); + // TODO: builtCtorList(GlobalDtors); + // TODO: buildGlobalAnnotations(); + // TODO: buildDeferredUnusedCoverageMappings(); + // TODO: CIRGenPGO + // TODO: CoverageMapping + if (getCodeGenOpts().SanitizeCfiCrossDso) { + llvm_unreachable("NYI"); + } + // TODO: buildAtAvailableLinkGuard(); + if (astCtx.getTargetInfo().getTriple().isWasm() && + !astCtx.getTargetInfo().getTriple().isOSEmscripten()) { + llvm_unreachable("NYI"); + } + + // Emit reference of __amdgpu_device_library_preserve_asan_functions to + // preserve ASAN functions in bitcode libraries. + if (getLangOpts().Sanitize.has(SanitizerKind::Address)) { + llvm_unreachable("NYI"); + } + + // TODO: buildLLVMUsed(); + // TODO: SanStats + + if (getCodeGenOpts().Autolink) { + // TODO: buildModuleLinkOptions + } + + // TODO: FINISH THE REST OF THIS +} + +bool CIRGenModule::shouldEmitFunction(GlobalDecl GD) { + // TODO: implement this -- requires defining linkage for CIR + return true; +} + +bool CIRGenModule::supportsCOMDAT() const { + return getTriple().supportsCOMDAT(); +} + +void CIRGenModule::maybeSetTrivialComdat(const Decl &D, mlir::Operation *Op) { + if (!shouldBeInCOMDAT(*this, D)) + return; + + // TODO: Op.setComdat + assert(!UnimplementedFeature::setComdat() && "NYI"); +} + +bool CIRGenModule::isInNoSanitizeList(SanitizerMask Kind, mlir::cir::FuncOp Fn, + SourceLocation Loc) const { + const auto &NoSanitizeL = getASTContext().getNoSanitizeList(); + // NoSanitize by function name. + if (NoSanitizeL.containsFunction(Kind, Fn.getName())) + llvm_unreachable("NYI"); + // NoSanitize by location. + if (Loc.isValid()) + return NoSanitizeL.containsLocation(Kind, Loc); + // If location is unknown, this may be a compiler-generated function. Assume + // it's located in the main file. + auto &SM = getASTContext().getSourceManager(); + if (const auto *MainFile = SM.getFileEntryForID(SM.getMainFileID())) { + return NoSanitizeL.containsFile(Kind, MainFile->getName()); + } + return false; +} + +void CIRGenModule::AddDeferredUnusedCoverageMapping(Decl *D) { + // Do we need to generate coverage mapping? + if (!codeGenOpts.CoverageMapping) + return; + + llvm_unreachable("NYI"); +} + +void CIRGenModule::UpdateCompletedType(const TagDecl *TD) { + // Make sure that this type is translated. + genTypes.UpdateCompletedType(TD); +} + +void CIRGenModule::addReplacement(StringRef Name, mlir::Operation *Op) { + Replacements[Name] = Op; +} + +void CIRGenModule::applyReplacements() { + for (auto &I : Replacements) { + StringRef MangledName = I.first(); + mlir::Operation *Replacement = I.second; + auto *Entry = getGlobalValue(MangledName); + if (!Entry) + continue; + assert(isa(Entry) && "expected function"); + auto OldF = cast(Entry); + auto NewF = dyn_cast(Replacement); + assert(NewF && "not implemented"); + + // Replace old with new, but keep the old order. + if (OldF.replaceAllSymbolUses(NewF.getSymNameAttr(), theModule).failed()) + llvm_unreachable("internal error, cannot RAUW symbol"); + if (NewF) { + NewF->moveBefore(OldF); + OldF->erase(); + } + } +} + +void CIRGenModule::buildExplicitCastExprType(const ExplicitCastExpr *E, + CIRGenFunction *CGF) { + // Bind VLAs in the cast type. + if (CGF && E->getType()->isVariablyModifiedType()) + llvm_unreachable("NYI"); + + assert(!UnimplementedFeature::generateDebugInfo() && "NYI"); +} + +void CIRGenModule::HandleCXXStaticMemberVarInstantiation(VarDecl *VD) { + auto DK = VD->isThisDeclarationADefinition(); + if (DK == VarDecl::Definition && VD->hasAttr()) + return; + + TemplateSpecializationKind TSK = VD->getTemplateSpecializationKind(); + // If we have a definition, this might be a deferred decl. If the + // instantiation is explicit, make sure we emit it at the end. + if (VD->getDefinition() && TSK == TSK_ExplicitInstantiationDefinition) { + llvm_unreachable("NYI"); + } + + buildTopLevelDecl(VD); +} + +mlir::cir::GlobalOp CIRGenModule::createOrReplaceCXXRuntimeVariable( + mlir::Location loc, StringRef Name, mlir::Type Ty, + mlir::cir::GlobalLinkageKind Linkage, clang::CharUnits Alignment) { + mlir::cir::GlobalOp OldGV{}; + auto GV = dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(getModule(), Name)); + + if (GV) { + // Check if the variable has the right type. + if (GV.getSymType() == Ty) + return GV; + + // Because C++ name mangling, the only way we can end up with an already + // existing global with the same name is if it has been declared extern + // "C". + assert(GV.isDeclaration() && "Declaration has wrong type!"); + OldGV = GV; + } + + // Create a new variable. + GV = CIRGenModule::createGlobalOp(*this, loc, Name, Ty); + + // Set up extra information and add to the module + GV.setLinkageAttr( + mlir::cir::GlobalLinkageKindAttr::get(builder.getContext(), Linkage)); + mlir::SymbolTable::setSymbolVisibility(GV, + CIRGenModule::getMLIRVisibility(GV)); + + if (OldGV) { + // Replace occurrences of the old variable if needed. + GV.setName(OldGV.getName()); + if (!OldGV->use_empty()) { + llvm_unreachable("NYI"); + } + OldGV->erase(); + } + + assert(!UnimplementedFeature::setComdat()); + if (supportsCOMDAT() && mlir::cir::isWeakForLinker(Linkage) && + !GV.hasAvailableExternallyLinkage()) + assert(!UnimplementedFeature::setComdat()); + + GV.setAlignmentAttr(getSize(Alignment)); + return GV; +} + +bool CIRGenModule::shouldOpportunisticallyEmitVTables() { + assert(codeGenOpts.OptimizationLevel == 0 && "NYI"); + return codeGenOpts.OptimizationLevel > 0; +} + +void CIRGenModule::buildVTableTypeMetadata(const CXXRecordDecl *RD, + mlir::cir::GlobalOp VTable, + const VTableLayout &VTLayout) { + if (!getCodeGenOpts().LTOUnit) + return; + llvm_unreachable("NYI"); +} + +mlir::Attribute CIRGenModule::getAddrOfRTTIDescriptor(mlir::Location loc, + QualType Ty, bool ForEH) { + // Return a bogus pointer if RTTI is disabled, unless it's for EH. + // FIXME: should we even be calling this method if RTTI is disabled + // and it's not for EH? + if ((!ForEH && !getLangOpts().RTTI) || getLangOpts().CUDAIsDevice || + (getLangOpts().OpenMP && getLangOpts().OpenMPIsTargetDevice && + getTriple().isNVPTX())) { + llvm_unreachable("NYI"); + } + + if (ForEH && Ty->isObjCObjectPointerType() && + getLangOpts().ObjCRuntime.isGNUFamily()) { + llvm_unreachable("NYI"); + } + + return getCXXABI().getAddrOfRTTIDescriptor(loc, Ty); +} + +/// TODO(cir): once we have cir.module, add this as a convenience method there. +/// +/// Look up the specified global in the module symbol table. +/// 1. If it does not exist, add a declaration of the global and return it. +/// 2. Else, the global exists but has the wrong type: return the function +/// with a constantexpr cast to the right type. +/// 3. Finally, if the existing global is the correct declaration, return the +/// existing global. +mlir::cir::GlobalOp CIRGenModule::getOrInsertGlobal( + mlir::Location loc, StringRef Name, mlir::Type Ty, + llvm::function_ref CreateGlobalCallback) { + // See if we have a definition for the specified global already. + auto GV = dyn_cast_or_null(getGlobalValue(Name)); + if (!GV) { + GV = CreateGlobalCallback(); + } + assert(GV && "The CreateGlobalCallback is expected to create a global"); + + // If the variable exists but has the wrong type, return a bitcast to the + // right type. + auto GVTy = GV.getSymType(); + assert(!UnimplementedFeature::addressSpace()); + auto PTy = builder.getPointerTo(Ty); + + if (GVTy != PTy) + llvm_unreachable("NYI"); + + // Otherwise, we just found the existing function or a prototype. + return GV; +} + +// Overload to construct a global variable using its constructor's defaults. +mlir::cir::GlobalOp CIRGenModule::getOrInsertGlobal(mlir::Location loc, + StringRef Name, + mlir::Type Ty) { + return getOrInsertGlobal(loc, Name, Ty, [&] { + return CIRGenModule::createGlobalOp(*this, loc, Name, + builder.getPointerTo(Ty)); + }); +} + +// TODO(cir): this can be shared with LLVM codegen. +CharUnits CIRGenModule::computeNonVirtualBaseClassOffset( + const CXXRecordDecl *DerivedClass, CastExpr::path_const_iterator Start, + CastExpr::path_const_iterator End) { + CharUnits Offset = CharUnits::Zero(); + + const ASTContext &Context = getASTContext(); + const CXXRecordDecl *RD = DerivedClass; + + for (CastExpr::path_const_iterator I = Start; I != End; ++I) { + const CXXBaseSpecifier *Base = *I; + assert(!Base->isVirtual() && "Should not see virtual bases here!"); + + // Get the layout. + const ASTRecordLayout &Layout = Context.getASTRecordLayout(RD); + + const auto *BaseDecl = + cast(Base->getType()->castAs()->getDecl()); + + // Add the offset. + Offset += Layout.getBaseClassOffset(BaseDecl); + + RD = BaseDecl; + } + + return Offset; +} + +void CIRGenModule::Error(SourceLocation loc, StringRef message) { + unsigned diagID = getDiags().getCustomDiagID(DiagnosticsEngine::Error, "%0"); + getDiags().Report(astCtx.getFullLoc(loc), diagID) << message; +} + +/// Print out an error that codegen doesn't support the specified stmt yet. +void CIRGenModule::ErrorUnsupported(const Stmt *S, const char *Type) { + unsigned DiagID = getDiags().getCustomDiagID(DiagnosticsEngine::Error, + "cannot compile this %0 yet"); + std::string Msg = Type; + getDiags().Report(astCtx.getFullLoc(S->getBeginLoc()), DiagID) + << Msg << S->getSourceRange(); +} + +/// Print out an error that codegen doesn't support the specified decl yet. +void CIRGenModule::ErrorUnsupported(const Decl *D, const char *Type) { + unsigned DiagID = getDiags().getCustomDiagID(DiagnosticsEngine::Error, + "cannot compile this %0 yet"); + std::string Msg = Type; + getDiags().Report(astCtx.getFullLoc(D->getLocation()), DiagID) << Msg; +} + +mlir::cir::SourceLanguage CIRGenModule::getCIRSourceLanguage() { + using ClangStd = clang::LangStandard; + using CIRLang = mlir::cir::SourceLanguage; + auto opts = getLangOpts(); + + if (opts.CPlusPlus || opts.CPlusPlus11 || opts.CPlusPlus14 || + opts.CPlusPlus17 || opts.CPlusPlus20 || opts.CPlusPlus23 || + opts.CPlusPlus26) + return CIRLang::CXX; + if (opts.C99 || opts.C11 || opts.C17 || opts.C23 || + opts.LangStd == ClangStd::lang_c89) + return CIRLang::C; + + // TODO(cir): support remaining source languages. + llvm_unreachable("CIR does not yet support the given source language"); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h new file mode 100644 index 000000000000..414e91f29649 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -0,0 +1,630 @@ +//===--- CIRGenModule.h - Per-Module state for CIR gen ----------*- C++ -*-===// +// +// 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 is the internal per-translation-unit state used for CIR translation. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CODEGEN_CIRGENMODULE_H +#define LLVM_CLANG_LIB_CODEGEN_CIRGENMODULE_H + +#include "CIRDataLayout.h" +#include "CIRGenBuilder.h" +#include "CIRGenCall.h" +#include "CIRGenTypeCache.h" +#include "CIRGenTypes.h" +#include "CIRGenVTables.h" +#include "CIRGenValue.h" +#include "UnimplementedFeatureGuarding.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/ADT/ScopedHashTable.h" +#include "llvm/ADT/SmallPtrSet.h" + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/Value.h" + +using namespace clang; +namespace cir { + +class CIRGenFunction; +class CIRGenCXXABI; +class TargetCIRGenInfo; + +enum ForDefinition_t : bool { NotForDefinition = false, ForDefinition = true }; + +/// Implementation of a CIR/MLIR emission from Clang AST. +/// +/// This will emit operations that are specific to C(++)/ObjC(++) language, +/// preserving the semantics of the language and (hopefully) allow to perform +/// accurate analysis and transformation based on these high level semantics. +class CIRGenModule : public CIRGenTypeCache { + CIRGenModule(CIRGenModule &) = delete; + CIRGenModule &operator=(CIRGenModule &) = delete; + +public: + CIRGenModule(mlir::MLIRContext &context, clang::ASTContext &astctx, + const clang::CodeGenOptions &CGO, + clang::DiagnosticsEngine &Diags); + + ~CIRGenModule(); + + const std::string &getModuleNameHash() const { return ModuleNameHash; } + +private: + mutable std::unique_ptr TheTargetCIRGenInfo; + + /// 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; + + /// Hold Clang AST information. + clang::ASTContext &astCtx; + + const clang::LangOptions &langOpts; + + const clang::CodeGenOptions &codeGenOpts; + + /// A "module" matches a c/cpp source file: containing a list of functions. + mlir::ModuleOp theModule; + + clang::DiagnosticsEngine &Diags; + + const clang::TargetInfo ⌖ + + std::unique_ptr ABI; + + /// Used for `UniqueInternalLinkageNames` option + std::string ModuleNameHash = ""; + + /// Per-module type mapping from clang AST to CIR. + CIRGenTypes genTypes; + + /// Holds information about C++ vtables. + CIRGenVTables VTables; + + /// Per-function codegen information. Updated everytime buildCIR is called + /// for FunctionDecls's. + CIRGenFunction *CurCGF = nullptr; + + // A set of references that have only been set via a weakref so far. This is + // used to remove the weak of the reference if we ever see a direct reference + // or a definition. + llvm::SmallPtrSet WeakRefReferences; + + /// ------- + /// Declaring variables + /// ------- + + /// Set of global decls for which we already diagnosed mangled name conflict. + /// Required to not issue a warning (on a mangling conflict) multiple times + /// for the same decl. + llvm::DenseSet DiagnosedConflictingDefinitions; + +public: + mlir::ModuleOp getModule() const { return theModule; } + CIRGenBuilderTy &getBuilder() { return builder; } + clang::ASTContext &getASTContext() const { return astCtx; } + const clang::TargetInfo &getTarget() const { return target; } + const clang::CodeGenOptions &getCodeGenOpts() const { return codeGenOpts; } + clang::DiagnosticsEngine &getDiags() const { return Diags; } + CIRGenTypes &getTypes() { return genTypes; } + const clang::LangOptions &getLangOpts() const { return langOpts; } + CIRGenFunction *getCurrCIRGenFun() const { return CurCGF; } + const CIRDataLayout getDataLayout() const { + // FIXME(cir): instead of creating a CIRDataLayout every time, set it as an + // attribute for the CIRModule class. + return {theModule}; + } + + CIRGenCXXABI &getCXXABI() const { return *ABI; } + + /// ------- + /// Handling globals + /// ------- + + // TODO(cir): does this really need to be a state for CIR emission? + GlobalDecl initializedGlobalDecl; + + /// Global variables with initializers that need to run before main. + /// TODO(cir): for now track a generation operation, this is so far only + /// used to sync with DelayedCXXInitPosition. Improve it when we actually + /// use function calls for initialization + std::vector CXXGlobalInits; + + /// Emit the function that initializes C++ globals. + void buildCXXGlobalInitFunc(); + + /// When a C++ decl with an initializer is deferred, null is + /// appended to CXXGlobalInits, and the index of that null is placed + /// here so that the initializer will be performed in the correct + /// order. Once the decl is emitted, the index is replaced with ~0U to ensure + /// that we don't re-emit the initializer. + llvm::DenseMap DelayedCXXInitPosition; + + /// Keep track of a map between lambda fields and names, this needs to be per + /// module since lambdas might get generated later as part of defered work, + /// and since the pointers are supposed to be uniqued, should be fine. Revisit + /// this if it ends up taking too much memory. + llvm::DenseMap LambdaFieldToName; + + /// If the declaration has internal linkage but is inside an + /// extern "C" linkage specification, prepare to emit an alias for it + /// to the expected name. + template + void maybeHandleStaticInExternC(const SomeDecl *D, mlir::cir::GlobalOp GV); + + /// Tell the consumer that this variable has been instantiated. + void HandleCXXStaticMemberVarInstantiation(VarDecl *VD); + + llvm::DenseMap StaticLocalDeclMap; + llvm::DenseMap Globals; + mlir::Operation *getGlobalValue(StringRef Ref); + mlir::Value getGlobalValue(const clang::Decl *D); + + /// If the specified mangled name is not in the module, create and return an + /// mlir::GlobalOp value + mlir::cir::GlobalOp + getOrCreateCIRGlobal(StringRef MangledName, mlir::Type Ty, LangAS AddrSpace, + const VarDecl *D, + ForDefinition_t IsForDefinition = NotForDefinition); + + mlir::cir::GlobalOp getStaticLocalDeclAddress(const VarDecl *D) { + return StaticLocalDeclMap[D]; + } + + void setStaticLocalDeclAddress(const VarDecl *D, mlir::cir::GlobalOp C) { + StaticLocalDeclMap[D] = C; + } + + mlir::cir::GlobalOp + getOrCreateStaticVarDecl(const VarDecl &D, + mlir::cir::GlobalLinkageKind Linkage); + + mlir::cir::GlobalOp buildGlobal(const VarDecl *D, mlir::Type Ty, + ForDefinition_t IsForDefinition); + + /// TODO(cir): once we have cir.module, add this as a convenience method + /// there instead of here. + /// + /// Look up the specified global in the module symbol table. + /// 1. If it does not exist, add a declaration of the global and return it. + /// 2. Else, the global exists but has the wrong type: return the function + /// with a constantexpr cast to the right type. + /// 3. Finally, if the existing global is the correct declaration, return + /// the existing global. + mlir::cir::GlobalOp getOrInsertGlobal( + mlir::Location loc, StringRef Name, mlir::Type Ty, + llvm::function_ref CreateGlobalCallback); + + // Overload to construct a global variable using its constructor's defaults. + mlir::cir::GlobalOp getOrInsertGlobal(mlir::Location loc, StringRef Name, + mlir::Type Ty); + + static mlir::cir::GlobalOp createGlobalOp(CIRGenModule &CGM, + mlir::Location loc, StringRef name, + mlir::Type t, bool isCst = false); + + /// Return the mlir::Value for the address of the given global variable. + /// If Ty is non-null and if the global doesn't exist, then it will be created + /// with the specified type instead of whatever the normal requested type + /// would be. If IsForDefinition is true, it is guaranteed that an actual + /// global with type Ty will be returned, not conversion of a variable with + /// the same mangled name but some other type. + mlir::Value + getAddrOfGlobalVar(const VarDecl *D, mlir::Type Ty = {}, + ForDefinition_t IsForDefinition = NotForDefinition); + + /// Return the mlir::GlobalViewAttr for the address of the given global. + mlir::cir::GlobalViewAttr + getAddrOfGlobalVarAttr(const VarDecl *D, mlir::Type Ty = {}, + ForDefinition_t IsForDefinition = NotForDefinition); + + /// Get a reference to the target of VD. + mlir::Operation* getWeakRefReference(const ValueDecl *VD); + + CharUnits + computeNonVirtualBaseClassOffset(const CXXRecordDecl *DerivedClass, + CastExpr::path_const_iterator Start, + CastExpr::path_const_iterator End); + + /// Get the CIR attributes and calling convention to use for a particular + /// function type. + /// + /// \param Name - The function name. + /// \param Info - The function type information. + /// \param CalleeInfo - The callee information these attributes are being + /// constructed for. If valid, the attributes applied to this decl may + /// contribute to the function attributes and calling convention. + /// \param Attrs [out] - On return, the attribute list to use. + void ConstructAttributeList(StringRef Name, const CIRGenFunctionInfo &Info, + CIRGenCalleeInfo CalleeInfo, + llvm::SmallSet &Attrs, + bool AttrOnCallSite, bool IsThunk); + + /// Will return a global variable of the given type. If a variable with a + /// different type already exists then a new variable with the right type + /// will be created and all uses of the old variable will be replaced with a + /// bitcast to the new variable. + mlir::cir::GlobalOp createOrReplaceCXXRuntimeVariable( + mlir::Location loc, StringRef Name, mlir::Type Ty, + mlir::cir::GlobalLinkageKind Linkage, clang::CharUnits Alignment); + + /// Emit any vtables which we deferred and still have a use for. + void buildDeferredVTables(); + bool shouldOpportunisticallyEmitVTables(); + + /// Return the appropriate linkage for the vtable, VTT, and type information + /// of the given class. + mlir::cir::GlobalLinkageKind getVTableLinkage(const CXXRecordDecl *RD); + + /// Emit type metadata for the given vtable using the given layout. + void buildVTableTypeMetadata(const CXXRecordDecl *RD, + mlir::cir::GlobalOp VTable, + const VTableLayout &VTLayout); + + /// Get the address of the RTTI descriptor for the given type. + mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc, QualType Ty, + bool ForEH = false); + + /// TODO(cir): add CIR visibility bits. + static mlir::SymbolTable::Visibility getCIRVisibility(Visibility V) { + switch (V) { + case DefaultVisibility: + return mlir::SymbolTable::Visibility::Public; + case HiddenVisibility: + return mlir::SymbolTable::Visibility::Private; + case ProtectedVisibility: + llvm_unreachable("NYI"); + } + llvm_unreachable("unknown visibility!"); + } + + llvm::DenseMap ConstantStringMap; + + /// Return a constant array for the given string. + mlir::Attribute getConstantArrayFromStringLiteral(const StringLiteral *E); + + /// Return a global symbol reference to a constant array for the given string + /// literal. + mlir::cir::GlobalViewAttr + getAddrOfConstantStringFromLiteral(const StringLiteral *S, + StringRef Name = ".str"); + unsigned StringLiteralCnt = 0; + + /// Return the AST address space of constant literal, which is used to emit + /// the constant literal as global variable in LLVM IR. + /// Note: This is not necessarily the address space of the constant literal + /// in AST. For address space agnostic language, e.g. C++, constant literal + /// in AST is always in default address space. + LangAS getGlobalConstantAddressSpace() const; + + /// Set attributes which are common to any form of a global definition (alias, + /// Objective-C method, function, global variable). + /// + /// NOTE: This should only be called for definitions. + void setCommonAttributes(GlobalDecl GD, mlir::Operation *GV); + + // TODO: this obviously overlaps with + const TargetCIRGenInfo &getTargetCIRGenInfo(); + + /// Helpers to convert Clang's SourceLocation to a MLIR Location. + mlir::Location getLoc(clang::SourceLocation SLoc); + mlir::Location getLoc(clang::SourceRange SLoc); + mlir::Location getLoc(mlir::Location lhs, mlir::Location rhs); + + /// Helper to convert Clang's alignment to CIR alignment + mlir::IntegerAttr getSize(CharUnits size); + + /// Returns whether the given record has public LTO visibility (regardless of + /// -lto-whole-program-visibility) and therefore may not participate in + /// (single-module) CFI and whole-program vtable optimization. + bool AlwaysHasLTOVisibilityPublic(const CXXRecordDecl *RD); + + /// Returns whether the given record has hidden LTO visibility and therefore + /// may participate in (single-module) CFI and whole-program vtable + /// optimization. + bool HasHiddenLTOVisibility(const CXXRecordDecl *RD); + + /// Determine whether an object of this type can be emitted + /// as a constant. + /// + /// If ExcludeCtor is true, the duration when the object's constructor runs + /// will not be considered. The caller will need to verify that the object is + /// not written to during its construction. + /// FIXME: in LLVM codegen path this is part of CGM, which doesn't seem + /// like necessary, since (1) it doesn't use CGM at all and (2) is AST type + /// query specific. + bool isTypeConstant(clang::QualType Ty, bool ExcludeCtor, bool ExcludeDtor); + + /// FIXME: this could likely be a common helper and not necessarily related + /// with codegen. + /// Return the best known alignment for an unknown pointer to a + /// particular class. + clang::CharUnits getClassPointerAlignment(const clang::CXXRecordDecl *RD); + + /// FIXME: this could likely be a common helper and not necessarily related + /// with codegen. + /// TODO: Add TBAAAccessInfo + clang::CharUnits getNaturalPointeeTypeAlignment(clang::QualType T, + LValueBaseInfo *BaseInfo); + + /// FIXME: this could likely be a common helper and not necessarily related + /// with codegen. + /// TODO: Add TBAAAccessInfo + clang::CharUnits getNaturalTypeAlignment(clang::QualType T, + LValueBaseInfo *BaseInfo = nullptr, + bool forPointeeType = false); + + mlir::cir::FuncOp getAddrOfCXXStructor( + clang::GlobalDecl GD, const CIRGenFunctionInfo *FnInfo = nullptr, + mlir::cir::FuncType FnType = nullptr, bool DontDefer = false, + ForDefinition_t IsForDefinition = NotForDefinition) { + + return getAddrAndTypeOfCXXStructor(GD, FnInfo, FnType, DontDefer, + IsForDefinition) + .second; + } + + /// A queue of (optional) vtables to consider emitting. + std::vector DeferredVTables; + + mlir::Type getVTableComponentType(); + CIRGenVTables &getVTables() { return VTables; } + + ItaniumVTableContext &getItaniumVTableContext() { + return VTables.getItaniumVTableContext(); + } + const ItaniumVTableContext &getItaniumVTableContext() const { + return VTables.getItaniumVTableContext(); + } + + /// This contains all the decls which have definitions but which are deferred + /// for emission and therefore should only be output if they are actually + /// used. If a decl is in this, then it is known to have not been referenced + /// yet. + std::map DeferredDecls; + + // This is a list of deferred decls which we have seen that *are* actually + // referenced. These get code generated when the module is done. + std::vector DeferredDeclsToEmit; + void addDeferredDeclToEmit(clang::GlobalDecl GD) { + DeferredDeclsToEmit.emplace_back(GD); + } + + // After HandleTranslation finishes, differently from DeferredDeclsToEmit, + // DefaultMethodsToEmit is only called after a set of CIR passes run. See + // addDefaultMethodsToEmit usage for examples. + std::vector DefaultMethodsToEmit; + void addDefaultMethodsToEmit(clang::GlobalDecl GD) { + DefaultMethodsToEmit.emplace_back(GD); + } + + std::pair getAddrAndTypeOfCXXStructor( + clang::GlobalDecl GD, const CIRGenFunctionInfo *FnInfo = nullptr, + mlir::cir::FuncType FnType = nullptr, bool Dontdefer = false, + ForDefinition_t IsForDefinition = NotForDefinition); + + void buildTopLevelDecl(clang::Decl *decl); + void buildLinkageSpec(const LinkageSpecDecl *D); + + /// Emit code for a single global function or var decl. Forward declarations + /// are emitted lazily. + void buildGlobal(clang::GlobalDecl D); + + mlir::Type getCIRType(const clang::QualType &type); + + /// Set the visibility for the given global. + void setGlobalVisibility(mlir::Operation *Op, const NamedDecl *D) const; + void setDSOLocal(mlir::Operation *Op) const; + /// Set visibility, dllimport/dllexport and dso_local. + /// This must be called after dllimport/dllexport is set. + void setGVProperties(mlir::Operation *Op, const NamedDecl *D) const; + void setGVPropertiesAux(mlir::Operation *Op, const NamedDecl *D) const; + + /// Determine whether the definition must be emitted; if this returns \c + /// false, the definition can be emitted lazily if it's used. + bool MustBeEmitted(const clang::ValueDecl *D); + + /// Whether this function's return type has no side effects, and thus may be + /// trivially discared if it is unused. + bool MayDropFunctionReturn(const clang::ASTContext &Context, + clang::QualType ReturnType); + + bool isInNoSanitizeList(clang::SanitizerMask Kind, mlir::cir::FuncOp Fn, + clang::SourceLocation) const; + + /// Determine whether the definition can be emitted eagerly, or should be + /// delayed until the end of the translation unit. This is relevant for + /// definitions whose linkage can change, e.g. implicit function instantions + /// which may later be explicitly instantiated. + bool MayBeEmittedEagerly(const clang::ValueDecl *D); + + bool verifyModule(); + + /// Return the address of the given function. If Ty is non-null, then this + /// function will use the specified type if it has to create it. + // TODO: this is a bit weird as `GetAddr` given we give back a FuncOp? + mlir::cir::FuncOp + GetAddrOfFunction(clang::GlobalDecl GD, mlir::Type Ty = nullptr, + bool ForVTable = false, bool Dontdefer = false, + ForDefinition_t IsForDefinition = NotForDefinition); + + mlir::Operation * + GetAddrOfGlobal(clang::GlobalDecl GD, + ForDefinition_t IsForDefinition = NotForDefinition); + + // C++ related functions. + void buildDeclContext(const DeclContext *DC); + + /// Return the result of value-initializing the given type, i.e. a null + /// expression of the given type. This is usually, but not always, an LLVM + /// null constant. + mlir::Value buildNullConstant(QualType T, mlir::Location loc); + + llvm::StringRef getMangledName(clang::GlobalDecl GD); + + void buildTentativeDefinition(const VarDecl *D); + + // Make sure that this type is translated. + void UpdateCompletedType(const clang::TagDecl *TD); + + void buildGlobalDefinition(clang::GlobalDecl D, + mlir::Operation *Op = nullptr); + void buildGlobalFunctionDefinition(clang::GlobalDecl D, mlir::Operation *Op); + void buildGlobalVarDefinition(const clang::VarDecl *D, + bool IsTentative = false); + + /// Emit the function that initializes the specified global + void buildGlobalVarDeclInit(const VarDecl *D, mlir::cir::GlobalOp Addr, + bool PerformInit); + + void addDeferredVTable(const CXXRecordDecl *RD) { + DeferredVTables.push_back(RD); + } + + /// Stored a deferred empty coverage mapping for an unused and thus + /// uninstrumented top level declaration. + void AddDeferredUnusedCoverageMapping(clang::Decl *D); + + std::nullptr_t getModuleDebugInfo() { return nullptr; } + + /// Emit any needed decls for which code generation was deferred. + void buildDeferred(unsigned recursionLimit); + + /// Helper for `buildDeferred` to apply actual codegen. + void buildGlobalDecl(clang::GlobalDecl &D); + + /// Build default methods not emitted before this point. + void buildDefaultMethods(); + + const llvm::Triple &getTriple() const { return target.getTriple(); } + + // Finalize CIR code generation. + void Release(); + + bool shouldEmitFunction(clang::GlobalDecl GD); + + // Produce code for this constructor/destructor. This method doesn't try to + // apply any ABI rules about which other constructors/destructors are needed + // or if they are alias to each other. + mlir::cir::FuncOp codegenCXXStructor(clang::GlobalDecl GD); + + // Produce code for this constructor/destructor for global initialzation. + void codegenGlobalInitCxxStructor(const clang::VarDecl *D, + mlir::cir::GlobalOp Addr, bool NeedsCtor, + bool NeedsDtor); + + bool lookupRepresentativeDecl(llvm::StringRef MangledName, + clang::GlobalDecl &Result) const; + + bool supportsCOMDAT() const; + void maybeSetTrivialComdat(const clang::Decl &D, mlir::Operation *Op); + + void emitError(const llvm::Twine &message) { theModule.emitError(message); } + + /// ------- + /// Visibility and Linkage + /// ------- + + static void setInitializer(mlir::cir::GlobalOp &op, mlir::Attribute value); + static mlir::SymbolTable::Visibility + getMLIRVisibilityFromCIRLinkage(mlir::cir::GlobalLinkageKind GLK); + static mlir::SymbolTable::Visibility + getMLIRVisibility(mlir::cir::GlobalOp op); + mlir::cir::GlobalLinkageKind getFunctionLinkage(GlobalDecl GD); + mlir::cir::GlobalLinkageKind + getCIRLinkageForDeclarator(const DeclaratorDecl *D, GVALinkage Linkage, + bool IsConstantVariable); + void setFunctionLinkage(GlobalDecl GD, mlir::cir::FuncOp f) { + auto L = getFunctionLinkage(GD); + f.setLinkageAttr( + mlir::cir::GlobalLinkageKindAttr::get(builder.getContext(), L)); + mlir::SymbolTable::setSymbolVisibility(f, + getMLIRVisibilityFromCIRLinkage(L)); + } + + mlir::cir::GlobalLinkageKind getCIRLinkageVarDefinition(const VarDecl *VD, + bool IsConstant); + + void addReplacement(StringRef Name, mlir::Operation *Op); + + mlir::Location getLocForFunction(const clang::FunctionDecl *FD); + + void ReplaceUsesOfNonProtoTypeWithRealFunction(mlir::Operation *Old, + mlir::cir::FuncOp NewFn); + + void setExtraAttributesForFunc(mlir::cir::FuncOp f, + const clang::FunctionDecl *FD); + + // TODO: CodeGen also passes an AttributeList here. We'll have to match that + // in CIR + mlir::cir::FuncOp + GetOrCreateCIRFunction(llvm::StringRef MangledName, mlir::Type Ty, + clang::GlobalDecl D, bool ForVTable, + bool DontDefer = false, bool IsThunk = false, + ForDefinition_t IsForDefinition = NotForDefinition); + // Effectively create the CIR instruction, properly handling insertion + // points. + mlir::cir::FuncOp createCIRFunction(mlir::Location loc, StringRef name, + mlir::cir::FuncType Ty, + const clang::FunctionDecl *FD); + + /// Emit type info if type of an expression is a variably modified + /// type. Also emit proper debug info for cast types. + void buildExplicitCastExprType(const ExplicitCastExpr *E, + CIRGenFunction *CGF = nullptr); + + static constexpr const char *builtinCoroId = "__builtin_coro_id"; + static constexpr const char *builtinCoroAlloc = "__builtin_coro_alloc"; + static constexpr const char *builtinCoroBegin = "__builtin_coro_begin"; + static constexpr const char *builtinCoroEnd = "__builtin_coro_end"; + + /// Given a builtin id for a function like "__builtin_fabsf", return a + /// Function* for "fabsf". + mlir::cir::FuncOp getBuiltinLibFunction(const FunctionDecl *FD, + unsigned BuiltinID); + + /// Emit a general error that something can't be done. + void Error(SourceLocation loc, StringRef error); + + /// Print out an error that codegen doesn't support the specified stmt yet. + void ErrorUnsupported(const Stmt *S, const char *Type); + + /// Print out an error that codegen doesn't support the specified decl yet. + void ErrorUnsupported(const Decl *D, const char *Type); + +private: + // An ordered map of canonical GlobalDecls to their mangled names. + llvm::MapVector MangledDeclNames; + llvm::StringMap Manglings; + + // FIXME: should we use llvm::TrackingVH here? + typedef llvm::StringMap ReplacementsTy; + ReplacementsTy Replacements; + /// Call replaceAllUsesWith on all pairs in Replacements. + void applyReplacements(); + + /// Map source language used to a CIR attribute. + mlir::cir::SourceLanguage getCIRSourceLanguage(); +}; +} // namespace cir + +#endif // LLVM_CLANG_LIB_CODEGEN_CIRGENMODULE_H diff --git a/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h new file mode 100644 index 000000000000..0a686181db61 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenRecordLayout.h @@ -0,0 +1,204 @@ +//===--- CIRGenRecordLayout.h - CIR Record Layout Information ---*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CIRGENRECORDLAYOUT_H +#define LLVM_CLANG_LIB_CIR_CIRGENRECORDLAYOUT_H + +#include "clang/AST/Decl.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/Support/raw_ostream.h" + +namespace cir { + +/// Structure with information about how a bitfield should be accessed. This is +/// very similar to what LLVM codegen does, once CIR evolves it's possible we +/// can use a more higher level representation. +/// TODO(cir): the comment below is extracted from LLVM, build a CIR version of +/// this. +/// +/// Often we layout a sequence of bitfields as a contiguous sequence of bits. +/// When the AST record layout does this, we represent it in the LLVM IR's type +/// as either a sequence of i8 members or a byte array to reserve the number of +/// bytes touched without forcing any particular alignment beyond the basic +/// character alignment. +/// +/// Then accessing a particular bitfield involves converting this byte array +/// into a single integer of that size (i24 or i40 -- may not be power-of-two +/// size), loading it, and shifting and masking to extract the particular +/// subsequence of bits which make up that particular bitfield. This structure +/// encodes the information used to construct the extraction code sequences. +/// The CIRGenRecordLayout also has a field index which encodes which +/// byte-sequence this bitfield falls within. Let's assume the following C +/// struct: +/// +/// struct S { +/// char a, b, c; +/// unsigned bits : 3; +/// unsigned more_bits : 4; +/// unsigned still_more_bits : 7; +/// }; +/// +/// This will end up as the following LLVM type. The first array is the +/// bitfield, and the second is the padding out to a 4-byte alignment. +/// +/// %t = type { i8, i8, i8, i8, i8, [3 x i8] } +/// +/// When generating code to access more_bits, we'll generate something +/// essentially like this: +/// +/// define i32 @foo(%t* %base) { +/// %0 = gep %t* %base, i32 0, i32 3 +/// %2 = load i8* %1 +/// %3 = lshr i8 %2, 3 +/// %4 = and i8 %3, 15 +/// %5 = zext i8 %4 to i32 +/// ret i32 %i +/// } +/// +struct CIRGenBitFieldInfo { + /// The offset within a contiguous run of bitfields that are represented as + /// a single "field" within the LLVM struct type. This offset is in bits. + unsigned Offset : 16; + + /// The total size of the bit-field, in bits. + unsigned Size : 15; + + /// Whether the bit-field is signed. + unsigned IsSigned : 1; + + /// The storage size in bits which should be used when accessing this + /// bitfield. + unsigned StorageSize; + + /// The offset of the bitfield storage from the start of the struct. + clang::CharUnits StorageOffset; + + /// The offset within a contiguous run of bitfields that are represented as a + /// single "field" within the LLVM struct type, taking into account the AAPCS + /// rules for volatile bitfields. This offset is in bits. + unsigned VolatileOffset : 16; + + /// The storage size in bits which should be used when accessing this + /// bitfield. + unsigned VolatileStorageSize; + + /// The offset of the bitfield storage from the start of the struct. + clang::CharUnits VolatileStorageOffset; + + CIRGenBitFieldInfo() + : Offset(), Size(), IsSigned(), StorageSize(), VolatileOffset(), + VolatileStorageSize() {} + + CIRGenBitFieldInfo(unsigned Offset, unsigned Size, bool IsSigned, + unsigned StorageSize, clang::CharUnits StorageOffset) + : Offset(Offset), Size(Size), IsSigned(IsSigned), + StorageSize(StorageSize), StorageOffset(StorageOffset) {} + + void print(llvm::raw_ostream &OS) const; + void dump() const; + + /// Given a bit-field decl, build an appropriate helper object for + /// accessing that field (which is expected to have the given offset and + /// size). + static CIRGenBitFieldInfo MakeInfo(class CIRGenTypes &Types, + const clang::FieldDecl *FD, + uint64_t Offset, uint64_t Size, + uint64_t StorageSize, + clang::CharUnits StorageOffset); +}; + +/// This class handles struct and union layout info while lowering AST types +/// to CIR types. +/// +/// These layout objects are only created on demand as CIR generation requires. +class CIRGenRecordLayout { + friend class CIRGenTypes; + + CIRGenRecordLayout(const CIRGenRecordLayout &) = delete; + void operator=(const CIRGenRecordLayout &) = delete; + +private: + /// The CIR type corresponding to this record layout; used when laying it out + /// as a complete object. + mlir::cir::StructType CompleteObjectType; + + /// The CIR type for the non-virtual part of this record layout; used when + /// laying it out as a base subobject. + mlir::cir::StructType BaseSubobjectType; + + /// Map from (non-bit-field) struct field to the corresponding cir struct type + /// field no. This info is populated by the record builder. + llvm::DenseMap FieldInfo; + + /// Map from (bit-field) struct field to the corresponding CIR struct type + /// field no. This info is populated by record builder. + /// TODO(CIR): value is an int for now, fix when we support bitfields + llvm::DenseMap BitFields; + + // FIXME: Maybe we could use CXXBaseSpecifier as the key and use a single map + // for both virtual and non-virtual bases. + llvm::DenseMap NonVirtualBases; + + /// Map from virtual bases to their field index in the complete object. + llvm::DenseMap + CompleteObjectVirtualBases; + + /// False if any direct or indirect subobject of this class, when considered + /// as a complete object, requires a non-zero bitpattern when + /// zero-initialized. + bool IsZeroInitializable : 1; + + /// False if any direct or indirect subobject of this class, when considered + /// as a base subobject, requires a non-zero bitpattern when zero-initialized. + bool IsZeroInitializableAsBase : 1; + +public: + CIRGenRecordLayout(mlir::cir::StructType CompleteObjectType, + mlir::cir::StructType BaseSubobjectType, + bool IsZeroInitializable, bool IsZeroInitializableAsBase) + : CompleteObjectType(CompleteObjectType), + BaseSubobjectType(BaseSubobjectType), + IsZeroInitializable(IsZeroInitializable), + IsZeroInitializableAsBase(IsZeroInitializableAsBase) {} + + /// Return the "complete object" LLVM type associated with + /// this record. + mlir::cir::StructType getCIRType() const { return CompleteObjectType; } + + /// Return the "base subobject" LLVM type associated with + /// this record. + mlir::cir::StructType getBaseSubobjectCIRType() const { + return BaseSubobjectType; + } + + /// Return cir::StructType element number that corresponds to the field FD. + unsigned getCIRFieldNo(const clang::FieldDecl *FD) const { + FD = FD->getCanonicalDecl(); + assert(FieldInfo.count(FD) && "Invalid field for record!"); + return FieldInfo.lookup(FD); + } + + /// Check whether this struct can be C++ zero-initialized with a + /// zeroinitializer. + bool isZeroInitializable() const { return IsZeroInitializable; } + + /// Return the BitFieldInfo that corresponds to the field FD. + const CIRGenBitFieldInfo &getBitFieldInfo(const clang::FieldDecl *FD) const { + FD = FD->getCanonicalDecl(); + assert(FD->isBitField() && "Invalid call for non-bit-field decl!"); + llvm::DenseMap::const_iterator + it = BitFields.find(FD); + assert(it != BitFields.end() && "Unable to find bitfield info"); + return it->second; + } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp new file mode 100644 index 000000000000..c3926ea99bf3 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -0,0 +1,1090 @@ +//===--- CIRGenStmt.cpp - Emit CIR Code from Statements -------------------===// +// +// 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 Stmt nodes as CIR code. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenFunction.h" + +using namespace cir; +using namespace clang; +using namespace mlir::cir; + +mlir::LogicalResult +CIRGenFunction::buildCompoundStmtWithoutScope(const CompoundStmt &S) { + for (auto *CurStmt : S.body()) + if (buildStmt(CurStmt, /*useCurrentScope=*/false).failed()) + return mlir::failure(); + + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildCompoundStmt(const CompoundStmt &S) { + mlir::LogicalResult res = mlir::success(); + + auto compoundStmtBuilder = [&]() -> mlir::LogicalResult { + if (buildCompoundStmtWithoutScope(S).failed()) + return mlir::failure(); + + return mlir::success(); + }; + + // Add local scope to track new declared variables. + SymTableScopeTy varScope(symbolTable); + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + LexicalScopeContext lexScope{loc, builder.getInsertionBlock()}; + LexicalScopeGuard lexScopeGuard{*this, &lexScope}; + res = compoundStmtBuilder(); + }); + + return res; +} + +void CIRGenFunction::buildStopPoint(const Stmt *S) { + assert(!UnimplementedFeature::generateDebugInfo()); +} + +// Build CIR for a statement. useCurrentScope should be true if no +// new scopes need be created when finding a compound statement. +mlir::LogicalResult CIRGenFunction::buildStmt(const Stmt *S, + bool useCurrentScope, + ArrayRef Attrs) { + if (mlir::succeeded(buildSimpleStmt(S, useCurrentScope))) + return mlir::success(); + + if (getContext().getLangOpts().OpenMP && + getContext().getLangOpts().OpenMPSimd) + assert(0 && "not implemented"); + + switch (S->getStmtClass()) { + case Stmt::NoStmtClass: + case Stmt::CXXCatchStmtClass: + case Stmt::SEHExceptStmtClass: + case Stmt::SEHFinallyStmtClass: + case Stmt::MSDependentExistsStmtClass: + llvm_unreachable("invalid statement class to emit generically"); + case Stmt::NullStmtClass: + case Stmt::CompoundStmtClass: + case Stmt::DeclStmtClass: + case Stmt::LabelStmtClass: + case Stmt::AttributedStmtClass: + case Stmt::GotoStmtClass: + case Stmt::BreakStmtClass: + case Stmt::ContinueStmtClass: + case Stmt::DefaultStmtClass: + case Stmt::CaseStmtClass: + case Stmt::SEHLeaveStmtClass: + llvm_unreachable("should have emitted these statements as simple"); + +#define STMT(Type, Base) +#define ABSTRACT_STMT(Op) +#define EXPR(Type, Base) case Stmt::Type##Class: +#include "clang/AST/StmtNodes.inc" + { + // Remember the block we came in on. + mlir::Block *incoming = builder.getInsertionBlock(); + assert(incoming && "expression emission must have an insertion point"); + + buildIgnoredExpr(cast(S)); + + mlir::Block *outgoing = builder.getInsertionBlock(); + assert(outgoing && "expression emission cleared block!"); + + // FIXME: Should we mimic LLVM emission here? + // The expression emitters assume (reasonably!) that the insertion + // point is always set. To maintain that, the call-emission code + // for noreturn functions has to enter a new block with no + // predecessors. We want to kill that block and mark the current + // insertion point unreachable in the common case of a call like + // "exit();". Since expression emission doesn't otherwise create + // blocks with no predecessors, we can just test for that. + // However, we must be careful not to do this to our incoming + // block, because *statement* emission does sometimes create + // reachable blocks which will have no predecessors until later in + // the function. This occurs with, e.g., labels that are not + // reachable by fallthrough. + if (incoming != outgoing && outgoing->use_empty()) + assert(0 && "not implemented"); + break; + } + + case Stmt::IfStmtClass: + if (buildIfStmt(cast(*S)).failed()) + return mlir::failure(); + break; + case Stmt::SwitchStmtClass: + if (buildSwitchStmt(cast(*S)).failed()) + return mlir::failure(); + break; + case Stmt::ForStmtClass: + if (buildForStmt(cast(*S)).failed()) + return mlir::failure(); + break; + case Stmt::WhileStmtClass: + if (buildWhileStmt(cast(*S)).failed()) + return mlir::failure(); + break; + case Stmt::DoStmtClass: + if (buildDoStmt(cast(*S)).failed()) + return mlir::failure(); + break; + + case Stmt::CoroutineBodyStmtClass: + return buildCoroutineBody(cast(*S)); + case Stmt::CoreturnStmtClass: + return buildCoreturnStmt(cast(*S)); + + case Stmt::CXXTryStmtClass: + return buildCXXTryStmt(cast(*S)); + + case Stmt::CXXForRangeStmtClass: + return buildCXXForRangeStmt(cast(*S), Attrs); + + case Stmt::IndirectGotoStmtClass: + case Stmt::ReturnStmtClass: + // When implemented, GCCAsmStmtClass should fall-through to MSAsmStmtClass. + case Stmt::GCCAsmStmtClass: + case Stmt::MSAsmStmtClass: + case Stmt::CapturedStmtClass: + case Stmt::ObjCAtTryStmtClass: + case Stmt::ObjCAtThrowStmtClass: + case Stmt::ObjCAtSynchronizedStmtClass: + case Stmt::ObjCForCollectionStmtClass: + case Stmt::ObjCAutoreleasePoolStmtClass: + case Stmt::SEHTryStmtClass: + case Stmt::OMPMetaDirectiveClass: + case Stmt::OMPCanonicalLoopClass: + case Stmt::OMPErrorDirectiveClass: + case Stmt::OMPParallelDirectiveClass: + case Stmt::OMPSimdDirectiveClass: + case Stmt::OMPTileDirectiveClass: + case Stmt::OMPUnrollDirectiveClass: + case Stmt::OMPForDirectiveClass: + case Stmt::OMPForSimdDirectiveClass: + case Stmt::OMPSectionsDirectiveClass: + case Stmt::OMPSectionDirectiveClass: + case Stmt::OMPSingleDirectiveClass: + case Stmt::OMPMasterDirectiveClass: + case Stmt::OMPCriticalDirectiveClass: + case Stmt::OMPParallelForDirectiveClass: + case Stmt::OMPParallelForSimdDirectiveClass: + case Stmt::OMPParallelMasterDirectiveClass: + case Stmt::OMPParallelSectionsDirectiveClass: + case Stmt::OMPTaskDirectiveClass: + case Stmt::OMPTaskyieldDirectiveClass: + case Stmt::OMPBarrierDirectiveClass: + case Stmt::OMPTaskwaitDirectiveClass: + case Stmt::OMPTaskgroupDirectiveClass: + case Stmt::OMPFlushDirectiveClass: + case Stmt::OMPDepobjDirectiveClass: + case Stmt::OMPScanDirectiveClass: + case Stmt::OMPOrderedDirectiveClass: + case Stmt::OMPAtomicDirectiveClass: + case Stmt::OMPTargetDirectiveClass: + case Stmt::OMPTeamsDirectiveClass: + case Stmt::OMPCancellationPointDirectiveClass: + case Stmt::OMPCancelDirectiveClass: + case Stmt::OMPTargetDataDirectiveClass: + case Stmt::OMPTargetEnterDataDirectiveClass: + case Stmt::OMPTargetExitDataDirectiveClass: + case Stmt::OMPTargetParallelDirectiveClass: + case Stmt::OMPTargetParallelForDirectiveClass: + case Stmt::OMPTaskLoopDirectiveClass: + case Stmt::OMPTaskLoopSimdDirectiveClass: + case Stmt::OMPMaskedTaskLoopDirectiveClass: + case Stmt::OMPMaskedTaskLoopSimdDirectiveClass: + case Stmt::OMPMasterTaskLoopDirectiveClass: + case Stmt::OMPMasterTaskLoopSimdDirectiveClass: + case Stmt::OMPParallelGenericLoopDirectiveClass: + case Stmt::OMPParallelMaskedDirectiveClass: + case Stmt::OMPParallelMaskedTaskLoopDirectiveClass: + case Stmt::OMPParallelMaskedTaskLoopSimdDirectiveClass: + case Stmt::OMPParallelMasterTaskLoopDirectiveClass: + case Stmt::OMPParallelMasterTaskLoopSimdDirectiveClass: + case Stmt::OMPDistributeDirectiveClass: + case Stmt::OMPDistributeParallelForDirectiveClass: + case Stmt::OMPDistributeParallelForSimdDirectiveClass: + case Stmt::OMPDistributeSimdDirectiveClass: + case Stmt::OMPTargetParallelGenericLoopDirectiveClass: + case Stmt::OMPTargetParallelForSimdDirectiveClass: + case Stmt::OMPTargetSimdDirectiveClass: + case Stmt::OMPTargetTeamsGenericLoopDirectiveClass: + case Stmt::OMPTargetUpdateDirectiveClass: + case Stmt::OMPTeamsDistributeDirectiveClass: + case Stmt::OMPTeamsDistributeSimdDirectiveClass: + case Stmt::OMPTeamsDistributeParallelForSimdDirectiveClass: + case Stmt::OMPTeamsDistributeParallelForDirectiveClass: + case Stmt::OMPTeamsGenericLoopDirectiveClass: + case Stmt::OMPTargetTeamsDirectiveClass: + case Stmt::OMPTargetTeamsDistributeDirectiveClass: + case Stmt::OMPTargetTeamsDistributeParallelForDirectiveClass: + case Stmt::OMPTargetTeamsDistributeParallelForSimdDirectiveClass: + case Stmt::OMPTargetTeamsDistributeSimdDirectiveClass: + case Stmt::OMPInteropDirectiveClass: + case Stmt::OMPDispatchDirectiveClass: + case Stmt::OMPGenericLoopDirectiveClass: + case Stmt::OMPScopeDirectiveClass: + case Stmt::OMPMaskedDirectiveClass: { + llvm::errs() << "CIR codegen for '" << S->getStmtClassName() + << "' not implemented\n"; + assert(0 && "not implemented"); + break; + } + case Stmt::ObjCAtCatchStmtClass: + llvm_unreachable( + "@catch statements should be handled by EmitObjCAtTryStmt"); + case Stmt::ObjCAtFinallyStmtClass: + llvm_unreachable( + "@finally statements should be handled by EmitObjCAtTryStmt"); + } + + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildSimpleStmt(const Stmt *S, + bool useCurrentScope) { + switch (S->getStmtClass()) { + default: + return mlir::failure(); + case Stmt::DeclStmtClass: + return buildDeclStmt(cast(*S)); + case Stmt::CompoundStmtClass: + return useCurrentScope + ? buildCompoundStmtWithoutScope(cast(*S)) + : buildCompoundStmt(cast(*S)); + case Stmt::ReturnStmtClass: + return buildReturnStmt(cast(*S)); + case Stmt::GotoStmtClass: + return buildGotoStmt(cast(*S)); + case Stmt::ContinueStmtClass: + return buildContinueStmt(cast(*S)); + + case Stmt::NullStmtClass: + break; + + case Stmt::LabelStmtClass: + return buildLabelStmt(cast(*S)); + + case Stmt::CaseStmtClass: + case Stmt::DefaultStmtClass: + assert(0 && + "Should not get here, currently handled directly from SwitchStmt"); + break; + + case Stmt::BreakStmtClass: + return buildBreakStmt(cast(*S)); + + case Stmt::AttributedStmtClass: + case Stmt::SEHLeaveStmtClass: + llvm::errs() << "CIR codegen for '" << S->getStmtClassName() + << "' not implemented\n"; + assert(0 && "not implemented"); + } + + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildLabelStmt(const clang::LabelStmt &S) { + if (buildLabel(S.getDecl()).failed()) + return mlir::failure(); + + // IsEHa: not implemented. + assert(!(getContext().getLangOpts().EHAsynch && S.isSideEntry())); + + return buildStmt(S.getSubStmt(), /* useCurrentScope */ true); +} + +// Add terminating yield on body regions (loops, ...) in case there are +// not other terminators used. +// FIXME: make terminateCaseRegion use this too. +static void terminateBody(mlir::OpBuilder &builder, mlir::Region &r, + mlir::Location loc) { + if (r.empty()) + return; + + SmallVector eraseBlocks; + unsigned numBlocks = r.getBlocks().size(); + for (auto &block : r.getBlocks()) { + // Already cleanup after return operations, which might create + // empty blocks if emitted as last stmt. + if (numBlocks != 1 && block.empty() && block.hasNoPredecessors() && + block.hasNoSuccessors()) + eraseBlocks.push_back(&block); + + if (block.empty() || + !block.back().hasTrait()) { + mlir::OpBuilder::InsertionGuard guardCase(builder); + builder.setInsertionPointToEnd(&block); + builder.create(loc); + } + } + + for (auto *b : eraseBlocks) + b->erase(); +} + +mlir::LogicalResult CIRGenFunction::buildIfStmt(const IfStmt &S) { + // The else branch of a consteval if statement is always the only branch + // that can be runtime evaluated. + if (S.isConsteval()) { + llvm_unreachable("consteval nyi"); + } + mlir::LogicalResult res = mlir::success(); + + // C99 6.8.4.1: The first substatement is executed if the expression + // compares unequal to 0. The condition must be a scalar type. + auto ifStmtBuilder = [&]() -> mlir::LogicalResult { + if (S.getInit()) + if (buildStmt(S.getInit(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + + if (S.getConditionVariable()) + buildDecl(*S.getConditionVariable()); + + // During LLVM codegen, if the condition constant folds and can be elided, + // it tries to avoid emitting the condition and the dead arm of the if/else. + // TODO(cir): we skip this in CIRGen, but should implement this as part of + // SSCP or a specific CIR pass. + bool CondConstant; + if (ConstantFoldsToSimpleInteger(S.getCond(), CondConstant, + S.isConstexpr())) { + assert(!UnimplementedFeature::constantFoldsToSimpleInteger()); + } + + assert(!UnimplementedFeature::emitCondLikelihoodViaExpectIntrinsic()); + assert(!UnimplementedFeature::incrementProfileCounter()); + return buildIfOnBoolExpr(S.getCond(), S.getThen(), S.getElse()); + }; + + // TODO: Add a new scoped symbol table. + // LexicalScope ConditionScope(*this, S.getCond()->getSourceRange()); + // The if scope contains the full source range for IfStmt. + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + LexicalScopeContext lexScope{scopeLoc, builder.getInsertionBlock()}; + LexicalScopeGuard lexIfScopeGuard{*this, &lexScope}; + res = ifStmtBuilder(); + }); + + return res; +} + +mlir::LogicalResult CIRGenFunction::buildDeclStmt(const DeclStmt &S) { + if (!builder.getInsertionBlock()) { + CGM.emitError("Seems like this is unreachable code, what should we do?"); + return mlir::failure(); + } + + for (const auto *I : S.decls()) { + buildDecl(*I); + } + + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildReturnStmt(const ReturnStmt &S) { + assert(!UnimplementedFeature::requiresReturnValueCheck()); + auto loc = getLoc(S.getSourceRange()); + + // Emit the result value, even if unused, to evaluate the side effects. + const Expr *RV = S.getRetValue(); + + // TODO(cir): LLVM codegen uses a RunCleanupsScope cleanupScope here, we + // should model this in face of dtors. + + bool createNewScope = false; + if (const auto *EWC = dyn_cast_or_null(RV)) { + RV = EWC->getSubExpr(); + createNewScope = true; + } + + auto handleReturnVal = [&]() { + if (getContext().getLangOpts().ElideConstructors && S.getNRVOCandidate() && + S.getNRVOCandidate()->isNRVOVariable()) { + assert(!UnimplementedFeature::openMP()); + // Apply the named return value optimization for this return statement, + // which means doing nothing: the appropriate result has already been + // constructed into the NRVO variable. + + // If there is an NRVO flag for this variable, set it to 1 into indicate + // that the cleanup code should not destroy the variable. + if (auto NRVOFlag = NRVOFlags[S.getNRVOCandidate()]) + getBuilder().createFlagStore(loc, true, NRVOFlag); + } else if (!ReturnValue.isValid() || (RV && RV->getType()->isVoidType())) { + // Make sure not to return anything, but evaluate the expression + // for side effects. + if (RV) { + assert(0 && "not implemented"); + } + } else if (!RV) { + // Do nothing (return value is left uninitialized) + } else if (FnRetTy->isReferenceType()) { + // If this function returns a reference, take the address of the + // expression rather than the value. + RValue Result = buildReferenceBindingToExpr(RV); + builder.create(loc, Result.getScalarVal(), + ReturnValue.getPointer()); + } else { + mlir::Value V = nullptr; + switch (CIRGenFunction::getEvaluationKind(RV->getType())) { + case TEK_Scalar: + V = buildScalarExpr(RV); + builder.create(loc, V, *FnRetAlloca); + break; + case TEK_Complex: + llvm_unreachable("NYI"); + break; + case TEK_Aggregate: + buildAggExpr( + RV, AggValueSlot::forAddr( + ReturnValue, Qualifiers(), AggValueSlot::IsDestructed, + AggValueSlot::DoesNotNeedGCBarriers, + AggValueSlot::IsNotAliased, getOverlapForReturnValue())); + break; + } + } + }; + + if (!createNewScope) + handleReturnVal(); + else { + mlir::Location scopeLoc = + getLoc(RV ? RV->getSourceRange() : S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + CIRGenFunction::LexicalScopeContext lexScope{ + loc, builder.getInsertionBlock()}; + CIRGenFunction::LexicalScopeGuard lexScopeGuard{*this, &lexScope}; + handleReturnVal(); + }); + } + + // Create a new return block (if not existent) and add a branch to + // it. The actual return instruction is only inserted during current + // scope cleanup handling. + auto *retBlock = currLexScope->getOrCreateRetBlock(*this, loc); + builder.create(loc, retBlock); + + // Insert the new block to continue codegen after branch to ret block. + builder.createBlock(builder.getBlock()->getParent()); + + // TODO(cir): LLVM codegen for a cleanup on cleanupScope here. + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildGotoStmt(const GotoStmt &S) { + // FIXME: LLVM codegen inserts emit stop point here for debug info + // sake when the insertion point is available, but doesn't do + // anything special when there isn't. We haven't implemented debug + // info support just yet, look at this again once we have it. + assert(builder.getInsertionBlock() && "not yet implemented"); + + // A goto marks the end of a block, create a new one for codegen after + // buildGotoStmt can resume building in that block. + + // Build a cir.br to the target label. + auto &JD = LabelMap[S.getLabel()]; + auto brOp = buildBranchThroughCleanup(getLoc(S.getSourceRange()), JD); + if (!JD.isValid()) + currLexScope->PendingGotos.push_back(std::make_pair(brOp, S.getLabel())); + + // Insert the new block to continue codegen after goto. + builder.createBlock(builder.getBlock()->getParent()); + + // What here... + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildLabel(const LabelDecl *D) { + JumpDest &Dest = LabelMap[D]; + + // Create a new block to tag with a label and add a branch from + // the current one to it. If the block is empty just call attach it + // to this label. + mlir::Block *currBlock = builder.getBlock(); + mlir::Block *labelBlock = currBlock; + if (!currBlock->empty()) { + + { + mlir::OpBuilder::InsertionGuard guard(builder); + labelBlock = builder.createBlock(builder.getBlock()->getParent()); + } + + builder.create(getLoc(D->getSourceRange()), labelBlock); + builder.setInsertionPointToEnd(labelBlock); + } + + if (!Dest.isValid()) { + Dest.Block = labelBlock; + currLexScope->SolvedLabels.insert(D); + // FIXME: add a label attribute to block... + } else { + assert(0 && "unimplemented"); + } + + // FIXME: emit debug info for labels, incrementProfileCounter + return mlir::success(); +} + +mlir::LogicalResult +CIRGenFunction::buildContinueStmt(const clang::ContinueStmt &S) { + builder.create( + getLoc(S.getContinueLoc()), + mlir::cir::YieldOpKindAttr::get(builder.getContext(), + mlir::cir::YieldOpKind::Continue), + mlir::ValueRange({})); + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildBreakStmt(const clang::BreakStmt &S) { + builder.create( + getLoc(S.getBreakLoc()), + mlir::cir::YieldOpKindAttr::get(builder.getContext(), + mlir::cir::YieldOpKind::Break), + mlir::ValueRange({})); + return mlir::success(); +} + +const CaseStmt * +CIRGenFunction::foldCaseStmt(const clang::CaseStmt &S, mlir::Type condType, + SmallVector &caseAttrs) { + const CaseStmt *caseStmt = &S; + const CaseStmt *lastCase = &S; + SmallVector caseEltValueListAttr; + + // Fold cascading cases whenever possible to simplify codegen a bit. + while (caseStmt) { + lastCase = caseStmt; + auto intVal = caseStmt->getLHS()->EvaluateKnownConstInt(getContext()); + caseEltValueListAttr.push_back(mlir::cir::IntAttr::get(condType, intVal)); + caseStmt = dyn_cast_or_null(caseStmt->getSubStmt()); + } + + auto *ctxt = builder.getContext(); + + auto caseAttr = mlir::cir::CaseAttr::get( + ctxt, builder.getArrayAttr(caseEltValueListAttr), + CaseOpKindAttr::get(ctxt, caseEltValueListAttr.size() > 1 + ? mlir::cir::CaseOpKind::Anyof + : mlir::cir::CaseOpKind::Equal)); + + caseAttrs.push_back(caseAttr); + + return lastCase; +} + +void CIRGenFunction::insertFallthrough(const clang::Stmt &S) { + builder.create( + getLoc(S.getBeginLoc()), + mlir::cir::YieldOpKindAttr::get(builder.getContext(), + mlir::cir::YieldOpKind::Fallthrough), + mlir::ValueRange({})); +} + +template +mlir::LogicalResult CIRGenFunction::buildCaseDefaultCascade( + const T *stmt, mlir::Type condType, + SmallVector &caseAttrs, mlir::OperationState &os) { + + assert((isa(stmt)) && + "only case or default stmt go here"); + + auto res = mlir::success(); + + // Update scope information with the current region we are + // emitting code for. This is useful to allow return blocks to be + // automatically and properly placed during cleanup. + auto *region = os.addRegion(); + auto *block = builder.createBlock(region); + builder.setInsertionPointToEnd(block); + currLexScope->updateCurrentSwitchCaseRegion(); + + auto *sub = stmt->getSubStmt(); + + if (isa(sub) && isa(stmt)) { + insertFallthrough(*stmt); + res = + buildDefaultStmt(*dyn_cast(sub), condType, caseAttrs, os); + } else if (isa(sub) && isa(stmt)) { + insertFallthrough(*stmt); + res = buildCaseStmt(*dyn_cast(sub), condType, caseAttrs, os); + } else { + mlir::OpBuilder::InsertionGuard guardCase(builder); + res = buildStmt(sub, /*useCurrentScope=*/!isa(sub)); + } + + return res; +} + +mlir::LogicalResult +CIRGenFunction::buildCaseStmt(const CaseStmt &S, mlir::Type condType, + SmallVector &caseAttrs, + mlir::OperationState &os) { + assert((!S.getRHS() || !S.caseStmtIsGNURange()) && + "case ranges not implemented"); + + auto *caseStmt = foldCaseStmt(S, condType, caseAttrs); + return buildCaseDefaultCascade(caseStmt, condType, caseAttrs, os); +} + +mlir::LogicalResult +CIRGenFunction::buildDefaultStmt(const DefaultStmt &S, mlir::Type condType, + SmallVector &caseAttrs, + mlir::OperationState &os) { + auto ctxt = builder.getContext(); + + auto defAttr = mlir::cir::CaseAttr::get( + ctxt, builder.getArrayAttr({}), + CaseOpKindAttr::get(ctxt, mlir::cir::CaseOpKind::Default)); + + caseAttrs.push_back(defAttr); + return buildCaseDefaultCascade(&S, condType, caseAttrs, os); +} + +static mlir::LogicalResult buildLoopCondYield(mlir::OpBuilder &builder, + mlir::Location loc, + mlir::Value cond) { + mlir::Block *trueBB = nullptr, *falseBB = nullptr; + { + mlir::OpBuilder::InsertionGuard guard(builder); + trueBB = builder.createBlock(builder.getBlock()->getParent()); + builder.create(loc, YieldOpKind::Continue); + } + { + mlir::OpBuilder::InsertionGuard guard(builder); + falseBB = builder.createBlock(builder.getBlock()->getParent()); + builder.create(loc); + } + + assert((trueBB && falseBB) && "expected both blocks to exist"); + builder.create(loc, cond, trueBB, falseBB); + return mlir::success(); +} + +mlir::LogicalResult +CIRGenFunction::buildCXXForRangeStmt(const CXXForRangeStmt &S, + ArrayRef ForAttrs) { + mlir::cir::LoopOp loopOp; + + // TODO(cir): pass in array of attributes. + auto forStmtBuilder = [&]() -> mlir::LogicalResult { + auto loopRes = mlir::success(); + // Evaluate the first pieces before the loop. + if (S.getInit()) + if (buildStmt(S.getInit(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + if (buildStmt(S.getRangeStmt(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + if (buildStmt(S.getBeginStmt(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + if (buildStmt(S.getEndStmt(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + + assert(!UnimplementedFeature::loopInfoStack()); + // From LLVM: if there are any cleanups between here and the loop-exit + // scope, create a block to stage a loop exit along. + // We probably already do the right thing because of ScopeOp, but make + // sure we handle all cases. + assert(!UnimplementedFeature::requiresCleanups()); + + loopOp = builder.create( + getLoc(S.getSourceRange()), mlir::cir::LoopOpKind::For, + /*condBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + assert(!UnimplementedFeature::createProfileWeightsForLoop()); + assert(!UnimplementedFeature::emitCondLikelihoodViaExpectIntrinsic()); + mlir::Value condVal = evaluateExprAsBool(S.getCond()); + if (buildLoopCondYield(b, loc, condVal).failed()) + loopRes = mlir::failure(); + }, + /*bodyBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + // https://en.cppreference.com/w/cpp/language/for + // In C++ the scope of the init-statement and the scope of + // statement are one and the same. + bool useCurrentScope = true; + if (buildStmt(S.getLoopVarStmt(), useCurrentScope).failed()) + loopRes = mlir::failure(); + if (buildStmt(S.getBody(), useCurrentScope).failed()) + loopRes = mlir::failure(); + buildStopPoint(&S); + }, + /*stepBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + if (S.getInc()) + if (buildStmt(S.getInc(), /*useCurrentScope=*/true).failed()) + loopRes = mlir::failure(); + builder.create(loc); + }); + return loopRes; + }; + + auto res = mlir::success(); + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + // Create a cleanup scope for the condition variable cleanups. + // Logical equivalent from LLVM codegn for + // LexicalScope ConditionScope(*this, S.getSourceRange())... + LexicalScopeContext lexScope{loc, builder.getInsertionBlock()}; + LexicalScopeGuard lexForScopeGuard{*this, &lexScope}; + res = forStmtBuilder(); + }); + + if (res.failed()) + return res; + + terminateBody(builder, loopOp.getBody(), getLoc(S.getEndLoc())); + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildForStmt(const ForStmt &S) { + mlir::cir::LoopOp loopOp; + + // TODO: pass in array of attributes. + auto forStmtBuilder = [&]() -> mlir::LogicalResult { + auto loopRes = mlir::success(); + // Evaluate the first part before the loop. + if (S.getInit()) + if (buildStmt(S.getInit(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + assert(!UnimplementedFeature::loopInfoStack()); + // From LLVM: if there are any cleanups between here and the loop-exit + // scope, create a block to stage a loop exit along. + // We probably already do the right thing because of ScopeOp, but make + // sure we handle all cases. + assert(!UnimplementedFeature::requiresCleanups()); + + loopOp = builder.create( + getLoc(S.getSourceRange()), mlir::cir::LoopOpKind::For, + /*condBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + assert(!UnimplementedFeature::createProfileWeightsForLoop()); + assert(!UnimplementedFeature::emitCondLikelihoodViaExpectIntrinsic()); + mlir::Value condVal; + if (S.getCond()) { + // If the for statement has a condition scope, + // emit the local variable declaration. + if (S.getConditionVariable()) + buildDecl(*S.getConditionVariable()); + // C99 6.8.5p2/p4: The first substatement is executed if the + // expression compares unequal to 0. The condition must be a + // scalar type. + condVal = evaluateExprAsBool(S.getCond()); + } else { + auto boolTy = mlir::cir::BoolType::get(b.getContext()); + condVal = b.create( + loc, boolTy, + mlir::cir::BoolAttr::get(b.getContext(), boolTy, true)); + } + if (buildLoopCondYield(b, loc, condVal).failed()) + loopRes = mlir::failure(); + }, + /*bodyBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + // https://en.cppreference.com/w/cpp/language/for + // While in C++, the scope of the init-statement and the scope of + // statement are one and the same, in C the scope of statement is + // nested within the scope of init-statement. + bool useCurrentScope = + CGM.getASTContext().getLangOpts().CPlusPlus ? true : false; + if (buildStmt(S.getBody(), useCurrentScope).failed()) + loopRes = mlir::failure(); + buildStopPoint(&S); + }, + /*stepBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + if (S.getInc()) + if (buildStmt(S.getInc(), /*useCurrentScope=*/true).failed()) + loopRes = mlir::failure(); + builder.create(loc); + }); + return loopRes; + }; + + auto res = mlir::success(); + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + LexicalScopeContext lexScope{loc, builder.getInsertionBlock()}; + LexicalScopeGuard lexForScopeGuard{*this, &lexScope}; + res = forStmtBuilder(); + }); + + if (res.failed()) + return res; + + terminateBody(builder, loopOp.getBody(), getLoc(S.getEndLoc())); + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildDoStmt(const DoStmt &S) { + mlir::cir::LoopOp loopOp; + + // TODO: pass in array of attributes. + auto doStmtBuilder = [&]() -> mlir::LogicalResult { + auto loopRes = mlir::success(); + assert(!UnimplementedFeature::loopInfoStack()); + // From LLVM: if there are any cleanups between here and the loop-exit + // scope, create a block to stage a loop exit along. + // We probably already do the right thing because of ScopeOp, but make + // sure we handle all cases. + assert(!UnimplementedFeature::requiresCleanups()); + + loopOp = builder.create( + getLoc(S.getSourceRange()), mlir::cir::LoopOpKind::DoWhile, + /*condBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + assert(!UnimplementedFeature::createProfileWeightsForLoop()); + assert(!UnimplementedFeature::emitCondLikelihoodViaExpectIntrinsic()); + // C99 6.8.5p2/p4: The first substatement is executed if the + // expression compares unequal to 0. The condition must be a + // scalar type. + mlir::Value condVal = evaluateExprAsBool(S.getCond()); + if (buildLoopCondYield(b, loc, condVal).failed()) + loopRes = mlir::failure(); + }, + /*bodyBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + if (buildStmt(S.getBody(), /*useCurrentScope=*/true).failed()) + loopRes = mlir::failure(); + buildStopPoint(&S); + }, + /*stepBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + builder.create(loc); + }); + return loopRes; + }; + + auto res = mlir::success(); + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + LexicalScopeContext lexScope{loc, builder.getInsertionBlock()}; + LexicalScopeGuard lexForScopeGuard{*this, &lexScope}; + res = doStmtBuilder(); + }); + + if (res.failed()) + return res; + + terminateBody(builder, loopOp.getBody(), getLoc(S.getEndLoc())); + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildWhileStmt(const WhileStmt &S) { + mlir::cir::LoopOp loopOp; + + // TODO: pass in array of attributes. + auto whileStmtBuilder = [&]() -> mlir::LogicalResult { + auto loopRes = mlir::success(); + assert(!UnimplementedFeature::loopInfoStack()); + // From LLVM: if there are any cleanups between here and the loop-exit + // scope, create a block to stage a loop exit along. + // We probably already do the right thing because of ScopeOp, but make + // sure we handle all cases. + assert(!UnimplementedFeature::requiresCleanups()); + + loopOp = builder.create( + getLoc(S.getSourceRange()), mlir::cir::LoopOpKind::While, + /*condBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + assert(!UnimplementedFeature::createProfileWeightsForLoop()); + assert(!UnimplementedFeature::emitCondLikelihoodViaExpectIntrinsic()); + mlir::Value condVal; + // If the for statement has a condition scope, + // emit the local variable declaration. + if (S.getConditionVariable()) + buildDecl(*S.getConditionVariable()); + // C99 6.8.5p2/p4: The first substatement is executed if the + // expression compares unequal to 0. The condition must be a + // scalar type. + condVal = evaluateExprAsBool(S.getCond()); + if (buildLoopCondYield(b, loc, condVal).failed()) + loopRes = mlir::failure(); + }, + /*bodyBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + if (buildStmt(S.getBody(), /*useCurrentScope=*/true).failed()) + loopRes = mlir::failure(); + buildStopPoint(&S); + }, + /*stepBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + builder.create(loc); + }); + return loopRes; + }; + + auto res = mlir::success(); + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + LexicalScopeContext lexScope{loc, builder.getInsertionBlock()}; + LexicalScopeGuard lexForScopeGuard{*this, &lexScope}; + res = whileStmtBuilder(); + }); + + if (res.failed()) + return res; + + terminateBody(builder, loopOp.getBody(), getLoc(S.getEndLoc())); + return mlir::success(); +} + +mlir::LogicalResult CIRGenFunction::buildSwitchStmt(const SwitchStmt &S) { + // TODO: LLVM codegen does some early optimization to fold the condition and + // only emit live cases. CIR should use MLIR to achieve similar things, + // nothing to be done here. + // if (ConstantFoldsToSimpleInteger(S.getCond(), ConstantCondValue))... + + auto res = mlir::success(); + SwitchOp swop; + + auto switchStmtBuilder = [&]() -> mlir::LogicalResult { + if (S.getInit()) + if (buildStmt(S.getInit(), /*useCurrentScope=*/true).failed()) + return mlir::failure(); + + if (S.getConditionVariable()) + buildDecl(*S.getConditionVariable()); + + mlir::Value condV = buildScalarExpr(S.getCond()); + + // TODO: PGO and likelihood (e.g. PGO.haveRegionCounts()) + // TODO: if the switch has a condition wrapped by __builtin_unpredictable? + + // FIXME: track switch to handle nested stmts. + swop = builder.create( + getLoc(S.getBeginLoc()), condV, + /*switchBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc, mlir::OperationState &os) { + auto *cs = dyn_cast(S.getBody()); + assert(cs && "expected compound stmt"); + SmallVector caseAttrs; + + currLexScope->setAsSwitch(); + mlir::Block *lastCaseBlock = nullptr; + for (auto *c : cs->body()) { + bool caseLike = isa(c); + if (!caseLike) { + // This means it's a random stmt following up a case, just + // emit it as part of previous known case. + assert(lastCaseBlock && "expects pre-existing case block"); + mlir::OpBuilder::InsertionGuard guardCase(builder); + builder.setInsertionPointToEnd(lastCaseBlock); + res = buildStmt(c, /*useCurrentScope=*/!isa(c)); + if (res.failed()) + break; + continue; + } + + auto *caseStmt = dyn_cast(c); + + if (caseStmt) + res = buildCaseStmt(*caseStmt, condV.getType(), caseAttrs, os); + else { + auto *defaultStmt = dyn_cast(c); + assert(defaultStmt && "expected default stmt"); + res = buildDefaultStmt(*defaultStmt, condV.getType(), caseAttrs, + os); + } + + lastCaseBlock = builder.getBlock(); + + if (res.failed()) + break; + } + + os.addAttribute("cases", builder.getArrayAttr(caseAttrs)); + }); + + if (res.failed()) + return res; + return mlir::success(); + }; + + // The switch scope contains the full source range for SwitchStmt. + auto scopeLoc = getLoc(S.getSourceRange()); + builder.create( + scopeLoc, /*scopeBuilder=*/ + [&](mlir::OpBuilder &b, mlir::Location loc) { + LexicalScopeContext lexScope{loc, builder.getInsertionBlock()}; + LexicalScopeGuard lexIfScopeGuard{*this, &lexScope}; + res = switchStmtBuilder(); + }); + + if (res.failed()) + return res; + + // Any block in a case region without a terminator is considered a + // fallthrough yield. In practice there shouldn't be more than one + // block without a terminator, we patch any block we see though and + // let mlir's SwitchOp verifier enforce rules. + auto terminateCaseRegion = [&](mlir::Region &r, mlir::Location loc) { + if (r.empty()) + return; + + SmallVector eraseBlocks; + unsigned numBlocks = r.getBlocks().size(); + for (auto &block : r.getBlocks()) { + // Already cleanup after return operations, which might create + // empty blocks if emitted as last stmt. + if (numBlocks != 1 && block.empty() && block.hasNoPredecessors() && + block.hasNoSuccessors()) + eraseBlocks.push_back(&block); + + if (block.empty() || + !block.back().hasTrait()) { + mlir::OpBuilder::InsertionGuard guardCase(builder); + builder.setInsertionPointToEnd(&block); + builder.create( + loc, + mlir::cir::YieldOpKindAttr::get( + builder.getContext(), mlir::cir::YieldOpKind::Fallthrough), + mlir::ValueRange({})); + } + } + + for (auto *b : eraseBlocks) + b->erase(); + }; + + // Make sure all case regions are terminated by inserting fallthroughs + // when necessary. + // FIXME: find a better way to get accurante with location here. + for (auto &r : swop.getRegions()) + terminateCaseRegion(r, swop.getLoc()); + return mlir::success(); +} + +void CIRGenFunction::buildReturnOfRValue(mlir::Location loc, RValue RV, + QualType Ty) { + if (RV.isScalar()) { + builder.createStore(loc, RV.getScalarVal(), ReturnValue); + } else if (RV.isAggregate()) { + LValue Dest = makeAddrLValue(ReturnValue, Ty); + LValue Src = makeAddrLValue(RV.getAggregateAddress(), Ty); + buildAggregateCopy(Dest, Src, Ty, getOverlapForReturnValue()); + } else { + llvm_unreachable("NYI"); + } + buildBranchThroughCleanup(loc, ReturnBlock()); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenTBAA.cpp b/clang/lib/CIR/CodeGen/CIRGenTBAA.cpp new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/clang/lib/CIR/CodeGen/CIRGenTBAA.h b/clang/lib/CIR/CodeGen/CIRGenTBAA.h new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/clang/lib/CIR/CodeGen/CIRGenTypeCache.h b/clang/lib/CIR/CodeGen/CIRGenTypeCache.h new file mode 100644 index 000000000000..cea3f07922e0 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenTypeCache.h @@ -0,0 +1,130 @@ +//===--- CIRGenTypeCache.h - Commonly used LLVM types and info -*- C++ --*-===// +// +// 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 structure provides a set of common types useful during CIR emission. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CODEGENTYPECACHE_H +#define LLVM_CLANG_LIB_CIR_CODEGENTYPECACHE_H + +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Types.h" +#include "clang/AST/CharUnits.h" +#include "clang/Basic/AddressSpaces.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +namespace cir { + +/// This structure provides a set of types that are commonly used +/// during IR emission. It's initialized once in CodeGenModule's +/// constructor and then copied around into new CIRGenFunction's. +struct CIRGenTypeCache { + CIRGenTypeCache() {} + + /// void + mlir::cir::VoidType VoidTy; + // char, int, short, long + mlir::cir::IntType SInt8Ty, SInt16Ty, SInt32Ty, SInt64Ty; + // usigned char, unsigned, unsigned short, unsigned long + mlir::cir::IntType UInt8Ty, UInt16Ty, UInt32Ty, UInt64Ty; + /// half, bfloat, float, double + // mlir::Type HalfTy, BFloatTy; + // TODO(cir): perhaps we should abstract long double variations into a custom + // cir.long_double type. Said type would also hold the semantics for lowering. + mlir::FloatType FloatTy, DoubleTy, LongDouble80BitsTy; + + /// int + mlir::Type UIntTy; + + /// char + mlir::Type UCharTy; + + /// intptr_t, size_t, and ptrdiff_t, which we assume are the same size. + union { + mlir::Type UIntPtrTy; + mlir::Type SizeTy; + mlir::Type PtrDiffTy; + }; + + /// void* in address space 0 + mlir::cir::PointerType VoidPtrTy; + mlir::cir::PointerType UInt8PtrTy; + + /// void** in address space 0 + union { + mlir::cir::PointerType VoidPtrPtrTy; + mlir::cir::PointerType UInt8PtrPtrTy; + }; + + /// void* in alloca address space + // union { + // mlir::cir::PointerType AllocaVoidPtrTy; + // mlir::cir::PointerType AllocaInt8PtrTy; + // }; + + /// void* in default globals address space + // union { + // mlir::cir::PointerType GlobalsVoidPtrTy; + // mlir::cir::PointerType GlobalsInt8PtrTy; + // }; + + /// void* in the address space for constant globals + // mlir::cir::PointerType ConstGlobalsPtrTy; + + /// The size and alignment of the builtin C type 'int'. This comes + /// up enough in various ABI lowering tasks to be worth pre-computing. + // union { + // unsigned char IntSizeInBytes; + // unsigned char IntAlignInBytes; + // }; + // clang::CharUnits getIntSize() const { + // return clang::CharUnits::fromQuantity(IntSizeInBytes); + // } + // clang::CharUnits getIntAlign() const { + // return clang::CharUnits::fromQuantity(IntAlignInBytes); + // } + + /// The width of a pointer into the generic address space. + // unsigned char PointerWidthInBits; + + /// The size and alignment of a pointer into the generic address space. + union { + unsigned char PointerAlignInBytes; + unsigned char PointerSizeInBytes; + }; + + /// The size and alignment of size_t. + // union { + // unsigned char SizeSizeInBytes; // sizeof(size_t) + // unsigned char SizeAlignInBytes; + // }; + + // clang::LangAS ASTAllocaAddressSpace; + + // clang::CharUnits getSizeSize() const { + // return clang::CharUnits::fromQuantity(SizeSizeInBytes); + // } + // clang::CharUnits getSizeAlign() const { + // return clang::CharUnits::fromQuantity(SizeAlignInBytes); + // } + clang::CharUnits getPointerSize() const { + return clang::CharUnits::fromQuantity(PointerSizeInBytes); + } + clang::CharUnits getPointerAlign() const { + return clang::CharUnits::fromQuantity(PointerAlignInBytes); + } + + // clang::LangAS getASTAllocaAddressSpace() const { + // return ASTAllocaAddressSpace; + // } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.cpp b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp new file mode 100644 index 000000000000..7d17c2cff842 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.cpp @@ -0,0 +1,860 @@ +#include "CIRGenTypes.h" +#include "CIRGenCall.h" +#include "CIRGenFunctionInfo.h" +#include "CIRGenModule.h" +#include "CallingConv.h" +#include "TargetInfo.h" + +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinTypes.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/Expr.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/RecordLayout.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace clang; +using namespace cir; + +unsigned CIRGenTypes::ClangCallConvToCIRCallConv(clang::CallingConv CC) { + assert(CC == CC_C && "No other calling conventions implemented."); + return cir::CallingConv::C; +} + +CIRGenTypes::CIRGenTypes(CIRGenModule &cgm) + : Context(cgm.getASTContext()), Builder(cgm.getBuilder()), CGM{cgm}, + Target(cgm.getTarget()), TheCXXABI(cgm.getCXXABI()), + TheABIInfo(cgm.getTargetCIRGenInfo().getABIInfo()) { + SkippedLayout = false; +} + +CIRGenTypes::~CIRGenTypes() { + for (llvm::FoldingSet::iterator I = FunctionInfos.begin(), + E = FunctionInfos.end(); + I != E;) + delete &*I++; +} + +// This is CIR's version of CIRGenTypes::addRecordTypeName +std::string CIRGenTypes::getRecordTypeName(const clang::RecordDecl *recordDecl, + StringRef suffix) { + llvm::SmallString<256> typeName; + llvm::raw_svector_ostream outStream(typeName); + + PrintingPolicy policy = recordDecl->getASTContext().getPrintingPolicy(); + policy.SuppressInlineNamespace = false; + + if (recordDecl->getIdentifier()) { + if (recordDecl->getDeclContext()) + recordDecl->printQualifiedName(outStream, policy); + else + recordDecl->printName(outStream, policy); + } else if (auto *typedefNameDecl = recordDecl->getTypedefNameForAnonDecl()) { + if (typedefNameDecl->getDeclContext()) + typedefNameDecl->printQualifiedName(outStream, policy); + else + typedefNameDecl->printName(outStream); + } else { + outStream << Builder.getUniqueAnonRecordName(); + } + + if (!suffix.empty()) + outStream << suffix; + + return std::string(typeName); +} + +/// Return true if the specified type is already completely laid out. +bool CIRGenTypes::isRecordLayoutComplete(const Type *Ty) const { + llvm::DenseMap::const_iterator I = + recordDeclTypes.find(Ty); + return I != recordDeclTypes.end() && I->second.isComplete(); +} + +static bool +isSafeToConvert(QualType T, CIRGenTypes &CGT, + llvm::SmallPtrSet &AlreadyChecked); + +/// Return true if it is safe to convert the specified record decl to IR and lay +/// it out, false if doing so would cause us to get into a recursive compilation +/// mess. +static bool +isSafeToConvert(const RecordDecl *RD, CIRGenTypes &CGT, + llvm::SmallPtrSet &AlreadyChecked) { + // If we have already checked this type (maybe the same type is used by-value + // multiple times in multiple structure fields, don't check again. + if (!AlreadyChecked.insert(RD).second) + return true; + + const Type *Key = CGT.getContext().getTagDeclType(RD).getTypePtr(); + + // If this type is already laid out, converting it is a noop. + if (CGT.isRecordLayoutComplete(Key)) + return true; + + // If this type is currently being laid out, we can't recursively compile it. + if (CGT.isRecordBeingLaidOut(Key)) + return false; + + // If this type would require laying out bases that are currently being laid + // out, don't do it. This includes virtual base classes which get laid out + // when a class is translated, even though they aren't embedded by-value into + // the class. + if (const CXXRecordDecl *CRD = dyn_cast(RD)) { + for (const auto &I : CRD->bases()) + if (!isSafeToConvert(I.getType()->castAs()->getDecl(), CGT, + AlreadyChecked)) + return false; + } + + // If this type would require laying out members that are currently being laid + // out, don't do it. + for (const auto *I : RD->fields()) + if (!isSafeToConvert(I->getType(), CGT, AlreadyChecked)) + return false; + + // If there are no problems, lets do it. + return true; +} + +/// Return true if it is safe to convert this field type, which requires the +/// structure elements contained by-value to all be recursively safe to convert. +static bool +isSafeToConvert(QualType T, CIRGenTypes &CGT, + llvm::SmallPtrSet &AlreadyChecked) { + // Strip off atomic type sugar. + if (const auto *AT = T->getAs()) + T = AT->getValueType(); + + // If this is a record, check it. + if (const auto *RT = T->getAs()) + return isSafeToConvert(RT->getDecl(), CGT, AlreadyChecked); + + // If this is an array, check the elements, which are embedded inline. + if (const auto *AT = CGT.getContext().getAsArrayType(T)) + return isSafeToConvert(AT->getElementType(), CGT, AlreadyChecked); + + // Otherwise, there is no concern about transforming this. We only care about + // things that are contained by-value in a structure that can have another + // structure as a member. + return true; +} + +// Return true if it is safe to convert the specified record decl to CIR and lay +// it out, false if doing so would cause us to get into a recursive compilation +// mess. +static bool isSafeToConvert(const RecordDecl *RD, CIRGenTypes &CGT) { + // If no structs are being laid out, we can certainly do this one. + if (CGT.noRecordsBeingLaidOut()) + return true; + + llvm::SmallPtrSet AlreadyChecked; + return isSafeToConvert(RD, CGT, AlreadyChecked); +} + +/// Lay out a tagged decl type like struct or union. +mlir::Type CIRGenTypes::convertRecordDeclType(const clang::RecordDecl *RD) { + // TagDecl's are not necessarily unique, instead use the (clang) type + // connected to the decl. + const auto *key = Context.getTagDeclType(RD).getTypePtr(); + mlir::cir::StructType entry = recordDeclTypes[key]; + + // Handle forward decl / incomplete types. + if (!entry) { + auto name = getRecordTypeName(RD, ""); + entry = Builder.getStructTy({}, name, /*incomplete=*/true, /*packed=*/false, + RD); + recordDeclTypes[key] = entry; + } + + RD = RD->getDefinition(); + if (!RD || !RD->isCompleteDefinition() || entry.isComplete()) + return entry; + + // If converting this type would cause us to infinitely loop, don't do it! + if (!isSafeToConvert(RD, *this)) { + DeferredRecords.push_back(RD); + return entry; + } + + // Okay, this is a definition of a type. Compile the implementation now. + bool InsertResult = RecordsBeingLaidOut.insert(key).second; + (void)InsertResult; + assert(InsertResult && "Recursively compiling a struct?"); + + // Force conversion of non-virtual base classes recursively. + if (const auto *cxxRecordDecl = dyn_cast(RD)) { + for (const auto &I : cxxRecordDecl->bases()) { + if (I.isVirtual()) + continue; + convertRecordDeclType(I.getType()->castAs()->getDecl()); + } + } + + // Layout fields. + std::unique_ptr Layout = computeRecordLayout(RD, &entry); + recordDeclTypes[key] = entry; + CIRGenRecordLayouts[key] = std::move(Layout); + + // We're done laying out this struct. + bool EraseResult = RecordsBeingLaidOut.erase(key); + (void)EraseResult; + assert(EraseResult && "struct not in RecordsBeingLaidOut set?"); + + // If this struct blocked a FunctionType conversion, then recompute whatever + // was derived from that. + // FIXME: This is hugely overconservative. + if (SkippedLayout) + TypeCache.clear(); + + // If we're done converting the outer-most record, then convert any deferred + // structs as well. + if (RecordsBeingLaidOut.empty()) + while (!DeferredRecords.empty()) + convertRecordDeclType(DeferredRecords.pop_back_val()); + + return entry; +} + +mlir::Type CIRGenTypes::convertTypeForMem(clang::QualType qualType, + bool forBitField) { + assert(!qualType->isConstantMatrixType() && "Matrix types NYI"); + + mlir::Type convertedType = ConvertType(qualType); + + assert(!forBitField && "Bit fields NYI"); + assert(!qualType->isBitIntType() && "BitIntType NYI"); + + return convertedType; +} + +mlir::MLIRContext &CIRGenTypes::getMLIRContext() const { + return *Builder.getContext(); +} + +mlir::Type CIRGenTypes::ConvertFunctionTypeInternal(QualType QFT) { + assert(QFT.isCanonical()); + const Type *Ty = QFT.getTypePtr(); + const FunctionType *FT = cast(QFT.getTypePtr()); + // First, check whether we can build the full fucntion type. If the function + // type depends on an incomplete type (e.g. a struct or enum), we cannot lower + // the function type. + assert(isFuncTypeConvertible(FT) && "NYI"); + + // While we're converting the parameter types for a function, we don't want to + // recursively convert any pointed-to structs. Converting directly-used + // structs is ok though. + assert(RecordsBeingLaidOut.insert(Ty).second && "NYI"); + + // The function type can be built; call the appropriate routines to build it + const CIRGenFunctionInfo *FI; + if (const auto *FPT = dyn_cast(FT)) { + FI = &arrangeFreeFunctionType( + CanQual::CreateUnsafe(QualType(FPT, 0))); + } else { + const FunctionNoProtoType *FNPT = cast(FT); + FI = &arrangeFreeFunctionType( + CanQual::CreateUnsafe(QualType(FNPT, 0))); + } + + mlir::Type ResultType = nullptr; + // If there is something higher level prodding our CIRGenFunctionInfo, then + // don't recurse into it again. + assert(!FunctionsBeingProcessed.count(FI) && "NYI"); + + // Otherwise, we're good to go, go ahead and convert it. + ResultType = GetFunctionType(*FI); + + RecordsBeingLaidOut.erase(Ty); + + assert(!SkippedLayout && "Shouldn't have skipped anything yet"); + + if (RecordsBeingLaidOut.empty()) + while (!DeferredRecords.empty()) + convertRecordDeclType(DeferredRecords.pop_back_val()); + + return ResultType; +} + +/// Return true if the specified type in a function parameter or result position +/// can be converted to a CIR type at this point. This boils down to being +/// whether it is complete, as well as whether we've temporarily deferred +/// expanding the type because we're in a recursive context. +bool CIRGenTypes::isFuncParamTypeConvertible(clang::QualType Ty) { + // Some ABIs cannot have their member pointers represented in LLVM IR unless + // certain circumstances have been reached. + assert(!Ty->getAs() && "NYI"); + + // If this isn't a tagged type, we can convert it! + const TagType *TT = Ty->getAs(); + if (!TT) + return true; + + // Incomplete types cannot be converted. + if (TT->isIncompleteType()) + return false; + + // If this is an enum, then it is always safe to convert. + const RecordType *RT = dyn_cast(TT); + if (!RT) + return true; + + // Otherwise, we have to be careful. If it is a struct that we're in the + // process of expanding, then we can't convert the function type. That's ok + // though because we must be in a pointer context under the struct, so we can + // just convert it to a dummy type. + // + // We decide this by checking whether ConvertRecordDeclType returns us an + // opaque type for a struct that we know is defined. + return isSafeToConvert(RT->getDecl(), *this); +} + +/// Code to verify a given function type is complete, i.e. the return type and +/// all of the parameter types are complete. Also check to see if we are in a +/// RS_StructPointer context, and if so whether any struct types have been +/// pended. If so, we don't want to ask the ABI lowering code to handle a type +/// that cannot be converted to a CIR type. +bool CIRGenTypes::isFuncTypeConvertible(const FunctionType *FT) { + if (!isFuncParamTypeConvertible(FT->getReturnType())) + return false; + + if (const auto *FPT = dyn_cast(FT)) + for (unsigned i = 0, e = FPT->getNumParams(); i != e; i++) + if (!isFuncParamTypeConvertible(FPT->getParamType(i))) + return false; + + return true; +} + +/// ConvertType - Convert the specified type to its MLIR form. +mlir::Type CIRGenTypes::ConvertType(QualType T) { + T = Context.getCanonicalType(T); + const Type *Ty = T.getTypePtr(); + + // For the device-side compilation, CUDA device builtin surface/texture types + // may be represented in different types. + assert(!Context.getLangOpts().CUDAIsDevice && "not implemented"); + + if (const auto *recordType = dyn_cast(T)) + return convertRecordDeclType(recordType->getDecl()); + + // See if type is already cached. + TypeCacheTy::iterator TCI = TypeCache.find(Ty); + // If type is found in map then use it. Otherwise, convert type T. + if (TCI != TypeCache.end()) + return TCI->second; + + // If we don't have it in the cache, convert it now. + mlir::Type ResultType = nullptr; + switch (Ty->getTypeClass()) { + case Type::Record: // Handled above. +#define TYPE(Class, Base) +#define ABSTRACT_TYPE(Class, Base) +#define NON_CANONICAL_TYPE(Class, Base) case Type::Class: +#define DEPENDENT_TYPE(Class, Base) case Type::Class: +#define NON_CANONICAL_UNLESS_DEPENDENT_TYPE(Class, Base) case Type::Class: +#include "clang/AST/TypeNodes.inc" + llvm_unreachable("Non-canonical or dependent types aren't possible."); + + case Type::Builtin: { + switch (cast(Ty)->getKind()) { + case BuiltinType::WasmExternRef: + case BuiltinType::SveBoolx2: + case BuiltinType::SveBoolx4: + case BuiltinType::SveCount: + llvm_unreachable("NYI"); + case BuiltinType::Void: + // TODO(cir): how should we model this? + ResultType = CGM.VoidTy; + break; + + case BuiltinType::ObjCId: + case BuiltinType::ObjCClass: + case BuiltinType::ObjCSel: + // TODO(cir): probably same as BuiltinType::Void + assert(0 && "not implemented"); + break; + + case BuiltinType::Bool: + ResultType = ::mlir::cir::BoolType::get(Builder.getContext()); + break; + + // Signed types. + case BuiltinType::Accum: + case BuiltinType::Char_S: + case BuiltinType::Fract: + case BuiltinType::Int: + case BuiltinType::Long: + case BuiltinType::LongAccum: + case BuiltinType::LongFract: + case BuiltinType::LongLong: + case BuiltinType::SChar: + case BuiltinType::Short: + case BuiltinType::ShortAccum: + case BuiltinType::ShortFract: + case BuiltinType::WChar_S: + // Saturated signed types. + case BuiltinType::SatAccum: + case BuiltinType::SatFract: + case BuiltinType::SatLongAccum: + case BuiltinType::SatLongFract: + case BuiltinType::SatShortAccum: + case BuiltinType::SatShortFract: + ResultType = + mlir::cir::IntType::get(Builder.getContext(), Context.getTypeSize(T), + /*isSigned=*/true); + break; + // Unsigned types. + case BuiltinType::Char16: + case BuiltinType::Char32: + case BuiltinType::Char8: + case BuiltinType::Char_U: + case BuiltinType::UAccum: + case BuiltinType::UChar: + case BuiltinType::UFract: + case BuiltinType::UInt: + case BuiltinType::ULong: + case BuiltinType::ULongAccum: + case BuiltinType::ULongFract: + case BuiltinType::ULongLong: + case BuiltinType::UShort: + case BuiltinType::UShortAccum: + case BuiltinType::UShortFract: + case BuiltinType::WChar_U: + // Saturated unsigned types. + case BuiltinType::SatUAccum: + case BuiltinType::SatUFract: + case BuiltinType::SatULongAccum: + case BuiltinType::SatULongFract: + case BuiltinType::SatUShortAccum: + case BuiltinType::SatUShortFract: + ResultType = + mlir::cir::IntType::get(Builder.getContext(), Context.getTypeSize(T), + /*isSigned=*/false); + break; + + case BuiltinType::Float16: + ResultType = Builder.getF16Type(); + break; + case BuiltinType::Half: + // Should be the same as above? + assert(0 && "not implemented"); + break; + case BuiltinType::BFloat16: + ResultType = Builder.getBF16Type(); + break; + case BuiltinType::Float: + ResultType = CGM.FloatTy; + break; + case BuiltinType::Double: + ResultType = CGM.DoubleTy; + break; + case BuiltinType::LongDouble: + ResultType = Builder.getFloatTyForFormat(Context.getFloatTypeSemantics(T), + /*useNativeHalf=*/false); + break; + case BuiltinType::Float128: + case BuiltinType::Ibm128: + // FIXME: look at Context.getFloatTypeSemantics(T) and getTypeForFormat + // on LLVM codegen. + assert(0 && "not implemented"); + break; + + case BuiltinType::NullPtr: + // Add proper CIR type for it? this looks mostly useful for sema related + // things (like for overloads accepting void), for now, given that + // `sizeof(std::nullptr_t)` is equal to `sizeof(void *)`, model + // std::nullptr_t as !cir.ptr + ResultType = Builder.getVoidPtrTy(); + break; + + case BuiltinType::UInt128: + case BuiltinType::Int128: + assert(0 && "not implemented"); + // FIXME: ResultType = Builder.getIntegerType(128); + break; + +#define IMAGE_TYPE(ImgType, Id, SingletonId, Access, Suffix) \ + case BuiltinType::Id: +#include "clang/Basic/OpenCLImageTypes.def" +#define EXT_OPAQUE_TYPE(ExtType, Id, Ext) case BuiltinType::Id: +#include "clang/Basic/OpenCLExtensionTypes.def" + case BuiltinType::OCLSampler: + case BuiltinType::OCLEvent: + case BuiltinType::OCLClkEvent: + case BuiltinType::OCLQueue: + case BuiltinType::OCLReserveID: + assert(0 && "not implemented"); + break; + case BuiltinType::SveInt8: + case BuiltinType::SveUint8: + case BuiltinType::SveInt8x2: + case BuiltinType::SveUint8x2: + case BuiltinType::SveInt8x3: + case BuiltinType::SveUint8x3: + case BuiltinType::SveInt8x4: + case BuiltinType::SveUint8x4: + case BuiltinType::SveInt16: + case BuiltinType::SveUint16: + case BuiltinType::SveInt16x2: + case BuiltinType::SveUint16x2: + case BuiltinType::SveInt16x3: + case BuiltinType::SveUint16x3: + case BuiltinType::SveInt16x4: + case BuiltinType::SveUint16x4: + case BuiltinType::SveInt32: + case BuiltinType::SveUint32: + case BuiltinType::SveInt32x2: + case BuiltinType::SveUint32x2: + case BuiltinType::SveInt32x3: + case BuiltinType::SveUint32x3: + case BuiltinType::SveInt32x4: + case BuiltinType::SveUint32x4: + case BuiltinType::SveInt64: + case BuiltinType::SveUint64: + case BuiltinType::SveInt64x2: + case BuiltinType::SveUint64x2: + case BuiltinType::SveInt64x3: + case BuiltinType::SveUint64x3: + case BuiltinType::SveInt64x4: + case BuiltinType::SveUint64x4: + case BuiltinType::SveBool: + case BuiltinType::SveFloat16: + case BuiltinType::SveFloat16x2: + case BuiltinType::SveFloat16x3: + case BuiltinType::SveFloat16x4: + case BuiltinType::SveFloat32: + case BuiltinType::SveFloat32x2: + case BuiltinType::SveFloat32x3: + case BuiltinType::SveFloat32x4: + case BuiltinType::SveFloat64: + case BuiltinType::SveFloat64x2: + case BuiltinType::SveFloat64x3: + case BuiltinType::SveFloat64x4: + case BuiltinType::SveBFloat16: + case BuiltinType::SveBFloat16x2: + case BuiltinType::SveBFloat16x3: + case BuiltinType::SveBFloat16x4: { + assert(0 && "not implemented"); + break; + } +#define PPC_VECTOR_TYPE(Name, Id, Size) \ + case BuiltinType::Id: \ + assert(0 && "not implemented"); \ + break; +#include "clang/Basic/PPCTypes.def" +#define RVV_TYPE(Name, Id, SingletonId) case BuiltinType::Id: +#include "clang/Basic/RISCVVTypes.def" + { + assert(0 && "not implemented"); + break; + } + case BuiltinType::Dependent: +#define BUILTIN_TYPE(Id, SingletonId) +#define PLACEHOLDER_TYPE(Id, SingletonId) case BuiltinType::Id: +#include "clang/AST/BuiltinTypes.def" + llvm_unreachable("Unexpected placeholder builtin type!"); + } + break; + } + case Type::Auto: + case Type::DeducedTemplateSpecialization: + llvm_unreachable("Unexpected undeduced type!"); + case Type::Complex: { + assert(0 && "not implemented"); + break; + } + case Type::LValueReference: + case Type::RValueReference: { + const ReferenceType *RTy = cast(Ty); + QualType ETy = RTy->getPointeeType(); + auto PointeeType = convertTypeForMem(ETy); + // TODO(cir): use Context.getTargetAddressSpace(ETy) on pointer + ResultType = + ::mlir::cir::PointerType::get(Builder.getContext(), PointeeType); + assert(ResultType && "Cannot get pointer type?"); + break; + } + case Type::Pointer: { + const PointerType *PTy = cast(Ty); + QualType ETy = PTy->getPointeeType(); + assert(!ETy->isConstantMatrixType() && "not implemented"); + + mlir::Type PointeeType = ConvertType(ETy); + + // Treat effectively as a *i8. + // if (PointeeType->isVoidTy()) + // PointeeType = Builder.getI8Type(); + + // FIXME: add address specifier to cir::PointerType? + ResultType = + ::mlir::cir::PointerType::get(Builder.getContext(), PointeeType); + assert(ResultType && "Cannot get pointer type?"); + break; + } + + case Type::VariableArray: { + assert(0 && "not implemented"); + break; + } + case Type::IncompleteArray: { + assert(0 && "not implemented"); + break; + } + case Type::ConstantArray: { + const ConstantArrayType *A = cast(Ty); + auto EltTy = convertTypeForMem(A->getElementType()); + + // FIXME(cir): add a `isSized` method to CIRGenBuilder. + auto isSized = [&](mlir::Type ty) { + if (ty.isIntOrFloat() || + ty.isa()) + return true; + assert(0 && "not implemented"); + return false; + }; + + // FIXME: In LLVM, "lower arrays of undefined struct type to arrays of + // i8 just to have a concrete type". Not sure this makes sense in CIR yet. + assert(isSized(EltTy) && "not implemented"); + ResultType = ::mlir::cir::ArrayType::get(Builder.getContext(), EltTy, + A->getSize().getZExtValue()); + break; + } + case Type::ExtVector: + case Type::Vector: { + assert(0 && "not implemented"); + break; + } + case Type::ConstantMatrix: { + assert(0 && "not implemented"); + break; + } + case Type::FunctionNoProto: + case Type::FunctionProto: + ResultType = ConvertFunctionTypeInternal(T); + break; + case Type::ObjCObject: + assert(0 && "not implemented"); + break; + + case Type::ObjCInterface: { + assert(0 && "not implemented"); + break; + } + + case Type::ObjCObjectPointer: { + assert(0 && "not implemented"); + break; + } + + case Type::Enum: { + const EnumDecl *ED = cast(Ty)->getDecl(); + if (ED->isCompleteDefinition() || ED->isFixed()) + return ConvertType(ED->getIntegerType()); + // Return a placeholder 'i32' type. This can be changed later when the + // type is defined (see UpdateCompletedType), but is likely to be the + // "right" answer. + ResultType = CGM.UInt32Ty; + break; + } + + case Type::BlockPointer: { + assert(0 && "not implemented"); + break; + } + + case Type::MemberPointer: { + assert(0 && "not implemented"); + break; + } + + case Type::Atomic: { + QualType valueType = cast(Ty)->getValueType(); + ResultType = convertTypeForMem(valueType); + + // Pad out to the inflated size if necessary. + uint64_t valueSize = Context.getTypeSize(valueType); + uint64_t atomicSize = Context.getTypeSize(Ty); + if (valueSize != atomicSize) { + llvm_unreachable("NYI"); + } + break; + } + case Type::Pipe: { + assert(0 && "not implemented"); + break; + } + case Type::BitInt: { + assert(0 && "not implemented"); + break; + } + } + + assert(ResultType && "Didn't convert a type?"); + + TypeCache[Ty] = ResultType; + return ResultType; +} + +const CIRGenFunctionInfo &CIRGenTypes::arrangeCIRFunctionInfo( + CanQualType resultType, bool instanceMethod, bool chainCall, + llvm::ArrayRef argTypes, FunctionType::ExtInfo info, + llvm::ArrayRef paramInfos, + RequiredArgs required) { + assert(llvm::all_of(argTypes, + [](CanQualType T) { return T.isCanonicalAsParam(); })); + + // Lookup or create unique function info. + llvm::FoldingSetNodeID ID; + CIRGenFunctionInfo::Profile(ID, instanceMethod, chainCall, info, paramInfos, + required, resultType, argTypes); + + void *insertPos = nullptr; + CIRGenFunctionInfo *FI = FunctionInfos.FindNodeOrInsertPos(ID, insertPos); + if (FI) + return *FI; + + unsigned CC = ClangCallConvToCIRCallConv(info.getCC()); + + // Construction the function info. We co-allocate the ArgInfos. + FI = CIRGenFunctionInfo::create(CC, instanceMethod, chainCall, info, + paramInfos, resultType, argTypes, required); + FunctionInfos.InsertNode(FI, insertPos); + + bool inserted = FunctionsBeingProcessed.insert(FI).second; + (void)inserted; + assert(inserted && "Recursively being processed?"); + + // Compute ABI inforamtion. + assert(info.getCC() != clang::CallingConv::CC_SpirFunction && "NYI"); + assert(info.getCC() != CC_Swift && info.getCC() != CC_SwiftAsync && + "Swift NYI"); + getABIInfo().computeInfo(*FI); + + // Loop over all of the computed argument and return value info. If any of + // them are direct or extend without a specified coerce type, specify the + // default now. + ABIArgInfo &retInfo = FI->getReturnInfo(); + if (retInfo.canHaveCoerceToType() && retInfo.getCoerceToType() == nullptr) + retInfo.setCoerceToType(ConvertType(FI->getReturnType())); + + for (auto &I : FI->arguments()) + if (I.info.canHaveCoerceToType() && I.info.getCoerceToType() == nullptr) + I.info.setCoerceToType(ConvertType(I.type)); + + bool erased = FunctionsBeingProcessed.erase(FI); + (void)erased; + assert(erased && "Not in set?"); + + return *FI; +} + +const CIRGenFunctionInfo &CIRGenTypes::arrangeGlobalDeclaration(GlobalDecl GD) { + assert(!dyn_cast(GD.getDecl()) && + "This is reported as a FIXME in LLVM codegen"); + const auto *FD = cast(GD.getDecl()); + + if (isa(GD.getDecl()) || + isa(GD.getDecl())) + return arrangeCXXStructorDeclaration(GD); + + return arrangeFunctionDeclaration(FD); +} + +// UpdateCompletedType - When we find the full definition for a TagDecl, +// replace the 'opaque' type we previously made for it if applicable. +void CIRGenTypes::UpdateCompletedType(const TagDecl *TD) { + // If this is an enum being completed, then we flush all non-struct types + // from the cache. This allows function types and other things that may be + // derived from the enum to be recomputed. + if (const auto *ED = dyn_cast(TD)) { + // Only flush the cache if we've actually already converted this type. + if (TypeCache.count(ED->getTypeForDecl())) { + // Okay, we formed some types based on this. We speculated that the enum + // would be lowered to i32, so we only need to flush the cache if this + // didn't happen. + if (!ConvertType(ED->getIntegerType()).isInteger(32)) + TypeCache.clear(); + } + // If necessary, provide the full definition of a type only used with a + // declaration so far. + assert(!UnimplementedFeature::generateDebugInfo()); + return; + } + + // If we completed a RecordDecl that we previously used and converted to an + // anonymous type, then go ahead and complete it now. + const auto *RD = cast(TD); + if (RD->isDependentType()) + return; + + // Only complete if we converted it already. If we haven't converted it yet, + // we'll just do it lazily. + if (recordDeclTypes.count(Context.getTagDeclType(RD).getTypePtr())) + convertRecordDeclType(RD); + + // If necessary, provide the full definition of a type only used with a + // declaration so far. + if (CGM.getModuleDebugInfo()) + llvm_unreachable("NYI"); +} + +/// getCIRGenRecordLayout - Return record layout info for the given record decl. +const CIRGenRecordLayout & +CIRGenTypes::getCIRGenRecordLayout(const RecordDecl *RD) { + const auto *Key = Context.getTagDeclType(RD).getTypePtr(); + + auto I = CIRGenRecordLayouts.find(Key); + if (I != CIRGenRecordLayouts.end()) + return *I->second; + + // Compute the type information. + convertRecordDeclType(RD); + + // Now try again. + I = CIRGenRecordLayouts.find(Key); + + assert(I != CIRGenRecordLayouts.end() && + "Unable to find record layout information for type"); + return *I->second; +} + +bool CIRGenTypes::isZeroInitializable(QualType T) { + if (T->getAs()) + return Context.getTargetNullPointerValue(T) == 0; + + if (const auto *AT = Context.getAsArrayType(T)) { + if (isa(AT)) + return true; + if (const auto *CAT = dyn_cast(AT)) + if (Context.getConstantArrayElementCount(CAT) == 0) + return true; + T = Context.getBaseElementType(T); + } + + // Records are non-zero-initializable if they contain any + // non-zero-initializable subobjects. + if (const RecordType *RT = T->getAs()) { + const RecordDecl *RD = RT->getDecl(); + return isZeroInitializable(RD); + } + + // We have to ask the ABI about member pointers. + if (const MemberPointerType *MPT = T->getAs()) + llvm_unreachable("NYI"); + + // Everything else is okay. + return true; +} + +bool CIRGenTypes::isZeroInitializable(const RecordDecl *RD) { + return getCIRGenRecordLayout(RD).isZeroInitializable(); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenTypes.h b/clang/lib/CIR/CodeGen/CIRGenTypes.h new file mode 100644 index 000000000000..1e54d287ec72 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenTypes.h @@ -0,0 +1,269 @@ +//===--- CIRGenTypes.h - Type translation for CIR CodeGen -------*- C++ -*-===// +// +// 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 is the code that handles AST -> CIR type lowering. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CODEGEN_CODEGENTYPES_H +#define LLVM_CLANG_LIB_CODEGEN_CODEGENTYPES_H + +#include "ABIInfo.h" +#include "CIRGenFunctionInfo.h" +#include "CIRGenRecordLayout.h" + +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/Type.h" +#include "clang/Basic/ABI.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/ADT/SmallPtrSet.h" + +#include "mlir/IR/MLIRContext.h" + +#include + +namespace llvm { +class FunctionType; +class DataLayout; +class Type; +class LLVMContext; +class StructType; +} // namespace llvm + +namespace clang { +class ASTContext; +template class CanQual; +class CXXConstructorDecl; +class CXXDestructorDecl; +class CXXMethodDecl; +class CodeGenOptions; +class FieldDecl; +class FunctionProtoType; +class ObjCInterfaceDecl; +class ObjCIvarDecl; +class PointerType; +class QualType; +class RecordDecl; +class TagDecl; +class TargetInfo; +class Type; +typedef CanQual CanQualType; +class GlobalDecl; + +} // end namespace clang + +namespace mlir { +class Type; +namespace cir { +class StructType; +} // namespace cir +} // namespace mlir + +namespace cir { +class CallArgList; +class CIRGenCXXABI; +class CIRGenModule; +class CIRGenFunctionInfo; +class CIRGenBuilderTy; + +/// This class organizes the cross-module state that is used while lowering +/// AST types to CIR types. +class CIRGenTypes { + clang::ASTContext &Context; + cir::CIRGenBuilderTy &Builder; + CIRGenModule &CGM; + const clang::TargetInfo &Target; + CIRGenCXXABI &TheCXXABI; + + // This should not be moved earlier, since its initialization depends on some + // of the previous reference members being already initialized + const ABIInfo &TheABIInfo; + + /// Contains the CIR type for any converted RecordDecl. + llvm::DenseMap> + CIRGenRecordLayouts; + + /// Contains the CIR type for any converted RecordDecl + llvm::DenseMap recordDeclTypes; + + /// Hold memoized CIRGenFunctionInfo results + llvm::FoldingSet FunctionInfos; + + /// This set keeps track of records that we're currently converting to a CIR + /// type. For example, when converting: + /// struct A { struct B { int x; } } when processing 'x', the 'A' and 'B' + /// types will be in this set. + llvm::SmallPtrSet RecordsBeingLaidOut; + + llvm::SmallPtrSet FunctionsBeingProcessed; + + /// True if we didn't layout a function due to being inside a recursive struct + /// conversion, set this to true. + bool SkippedLayout; + + llvm::SmallVector DeferredRecords; + + /// Heper for ConvertType. + mlir::Type ConvertFunctionTypeInternal(clang::QualType FT); + +public: + CIRGenTypes(CIRGenModule &cgm); + ~CIRGenTypes(); + + cir::CIRGenBuilderTy &getBuilder() const { return Builder; } + CIRGenModule &getModule() const { return CGM; } + + /// Utility to check whether a function type can be converted to a CIR type + /// (i.e. doesn't depend on an incomplete tag type). + bool isFuncTypeConvertible(const clang::FunctionType *FT); + bool isFuncParamTypeConvertible(clang::QualType Ty); + + /// Convert clang calling convention to LLVM calling convention. + unsigned ClangCallConvToCIRCallConv(clang::CallingConv CC); + + /// Derives the 'this' type for CIRGen purposes, i.e. ignoring method CVR + /// qualification. + clang::CanQualType DeriveThisType(const clang::CXXRecordDecl *RD, + const clang::CXXMethodDecl *MD); + + /// This map keeps cache of llvm::Types and maps clang::Type to + /// corresponding llvm::Type. + using TypeCacheTy = llvm::DenseMap; + TypeCacheTy TypeCache; + + clang::ASTContext &getContext() const { return Context; } + mlir::MLIRContext &getMLIRContext() const; + + bool isRecordLayoutComplete(const clang::Type *Ty) const; + bool noRecordsBeingLaidOut() const { return RecordsBeingLaidOut.empty(); } + bool isRecordBeingLaidOut(const clang::Type *Ty) const { + return RecordsBeingLaidOut.count(Ty); + } + + /// Return whether a type can be zero-initialized (in the C++ sense) with an + /// LLVM zeroinitializer. + bool isZeroInitializable(clang::QualType T); + /// Return whether a record type can be zero-initialized (in the C++ sense) + /// with an LLVM zeroinitializer. + bool isZeroInitializable(const clang::RecordDecl *RD); + + const ABIInfo &getABIInfo() const { return TheABIInfo; } + CIRGenCXXABI &getCXXABI() const { return TheCXXABI; } + + /// Convert type T into a mlir::Type. + mlir::Type ConvertType(clang::QualType T); + + mlir::Type convertRecordDeclType(const clang::RecordDecl *recordDecl); + + std::unique_ptr + computeRecordLayout(const clang::RecordDecl *D, mlir::cir::StructType *Ty); + + std::string getRecordTypeName(const clang::RecordDecl *, + llvm::StringRef suffix); + + /// Determine if a C++ inheriting constructor should have parameters matching + /// those of its inherited constructor. + bool inheritingCtorHasParams(const clang::InheritedConstructor &Inherited, + clang::CXXCtorType Type); + + const CIRGenRecordLayout &getCIRGenRecordLayout(const clang::RecordDecl *RD); + + /// Convert type T into an mlir::Type. This differs from + /// convertType in that it is used to convert to the memory representation + /// for a type. For example, the scalar representation for _Bool is i1, but + /// the memory representation is usually i8 or i32, depending on the target. + // TODO: convert this comment to account for MLIR's equivalence + mlir::Type convertTypeForMem(clang::QualType, bool forBitField = false); + + /// Get the CIR function type for \arg Info. + mlir::cir::FuncType GetFunctionType(const CIRGenFunctionInfo &Info); + + mlir::cir::FuncType GetFunctionType(clang::GlobalDecl GD); + + /// Get the LLVM function type for use in a vtable, given a CXXMethodDecl. If + /// the method to has an incomplete return type, and/or incomplete argument + /// types, this will return the opaque type. + mlir::cir::FuncType GetFunctionTypeForVTable(clang::GlobalDecl GD); + + // The arrangement methods are split into three families: + // - those meant to drive the signature and prologue/epilogue + // of a function declaration or definition, + // - those meant for the computation of the CIR type for an abstract + // appearance of a function, and + // - those meant for performing the CIR-generation of a call. + // They differ mainly in how they deal with optional (i.e. variadic) + // arguments, as well as unprototyped functions. + // + // Key points: + // - The CIRGenFunctionInfo for emitting a specific call site must include + // entries for the optional arguments. + // - The function type used at the call site must reflect the formal + // signature + // of the declaration being called, or else the call will go away. + // - For the most part, unprototyped functions are called by casting to a + // formal signature inferred from the specific argument types used at the + // call-site. However, some targets (e.g. x86-64) screw with this for + // compatability reasons. + + const CIRGenFunctionInfo &arrangeGlobalDeclaration(clang::GlobalDecl GD); + + /// UpdateCompletedType - when we find the full definition for a TagDecl, + /// replace the 'opaque' type we previously made for it if applicable. + void UpdateCompletedType(const clang::TagDecl *TD); + + /// Free functions are functions that are compatible with an ordinary C + /// function pointer type. + const CIRGenFunctionInfo & + arrangeFunctionDeclaration(const clang::FunctionDecl *FD); + + const CIRGenFunctionInfo &arrangeCXXConstructorCall( + const CallArgList &Args, const clang::CXXConstructorDecl *D, + clang::CXXCtorType CtorKind, unsigned ExtraPrefixArgs, + unsigned ExtraSuffixArgs, bool PassProtoArgs = true); + + const CIRGenFunctionInfo & + arrangeCXXMethodCall(const CallArgList &args, + const clang::FunctionProtoType *type, + RequiredArgs required, unsigned numPrefixArgs); + + /// C++ methods have some special rules and also have implicit parameters. + const CIRGenFunctionInfo & + arrangeCXXMethodDeclaration(const clang::CXXMethodDecl *MD); + const CIRGenFunctionInfo &arrangeCXXStructorDeclaration(clang::GlobalDecl GD); + + const CIRGenFunctionInfo & + arrangeCXXMethodType(const clang::CXXRecordDecl *RD, + const clang::FunctionProtoType *FTP, + const clang::CXXMethodDecl *MD); + + const CIRGenFunctionInfo & + arrangeFreeFunctionCall(const CallArgList &Args, + const clang::FunctionType *Ty, bool ChainCall); + + const CIRGenFunctionInfo & + arrangeFreeFunctionType(clang::CanQual Ty); + + const CIRGenFunctionInfo & + arrangeFreeFunctionType(clang::CanQual FTNP); + + /// "Arrange" the CIR information for a call or type with the given + /// signature. This is largely an internal method; other clients should use + /// one of the above routines, which ultimatley defer to this. + /// + /// \param argTypes - must all actually be canonical as params + const CIRGenFunctionInfo &arrangeCIRFunctionInfo( + clang::CanQualType returnType, bool instanceMethod, bool chainCall, + llvm::ArrayRef argTypes, + clang::FunctionType::ExtInfo info, + llvm::ArrayRef paramInfos, + RequiredArgs args); +}; +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp new file mode 100644 index 000000000000..3f2232c675cb --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -0,0 +1,586 @@ +//===--- CIRGenVTables.cpp - Emit CIR Code for C++ vtables ----------------===// +// +// 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 dealing with C++ code generation of virtual tables. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenCXXABI.h" +#include "CIRGenFunction.h" +#include "CIRGenModule.h" +#include "mlir/IR/Attributes.h" +#include "clang/AST/Attr.h" +#include "clang/AST/CXXInheritance.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/VTTBuilder.h" +#include "clang/Basic/CodeGenOptions.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "clang/CodeGen/CGFunctionInfo.h" +#include "clang/CodeGen/ConstantInitBuilder.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Format.h" +#include "llvm/Transforms/Utils/Cloning.h" +#include +#include + +using namespace clang; +using namespace cir; + +CIRGenVTables::CIRGenVTables(CIRGenModule &CGM) + : CGM(CGM), VTContext(CGM.getASTContext().getVTableContext()) {} + +static bool UseRelativeLayout(const CIRGenModule &CGM) { + return CGM.getTarget().getCXXABI().isItaniumFamily() && + CGM.getItaniumVTableContext().isRelativeLayout(); +} + +bool CIRGenVTables::useRelativeLayout() const { return UseRelativeLayout(CGM); } + +mlir::Type CIRGenModule::getVTableComponentType() { + mlir::Type ptrTy = builder.getUInt8PtrTy(); + if (UseRelativeLayout(*this)) + ptrTy = builder.getUInt32PtrTy(); + return ptrTy; +} + +mlir::Type CIRGenVTables::getVTableComponentType() { + return CGM.getVTableComponentType(); +} + +mlir::Type CIRGenVTables::getVTableType(const VTableLayout &layout) { + SmallVector tys; + auto ctx = CGM.getBuilder().getContext(); + auto componentType = getVTableComponentType(); + for (unsigned i = 0, e = layout.getNumVTables(); i != e; ++i) + tys.push_back( + mlir::cir::ArrayType::get(ctx, componentType, layout.getVTableSize(i))); + + // FIXME(cir): should VTableLayout be encoded like we do for some + // AST nodes? + return CGM.getBuilder().getAnonStructTy(tys, /*incomplete=*/false); +} + +/// At this point in the translation unit, does it appear that can we +/// rely on the vtable being defined elsewhere in the program? +/// +/// The response is really only definitive when called at the end of +/// the translation unit. +/// +/// The only semantic restriction here is that the object file should +/// not contain a vtable definition when that vtable is defined +/// strongly elsewhere. Otherwise, we'd just like to avoid emitting +/// vtables when unnecessary. +/// TODO(cir): this should be merged into common AST helper for codegen. +bool CIRGenVTables::isVTableExternal(const CXXRecordDecl *RD) { + assert(RD->isDynamicClass() && "Non-dynamic classes have no VTable."); + + // We always synthesize vtables if they are needed in the MS ABI. MSVC doesn't + // emit them even if there is an explicit template instantiation. + if (CGM.getTarget().getCXXABI().isMicrosoft()) + return false; + + // If we have an explicit instantiation declaration (and not a + // definition), the vtable is defined elsewhere. + TemplateSpecializationKind TSK = RD->getTemplateSpecializationKind(); + if (TSK == TSK_ExplicitInstantiationDeclaration) + return true; + + // Otherwise, if the class is an instantiated template, the + // vtable must be defined here. + if (TSK == TSK_ImplicitInstantiation || + TSK == TSK_ExplicitInstantiationDefinition) + return false; + + // Otherwise, if the class doesn't have a key function (possibly + // anymore), the vtable must be defined here. + const CXXMethodDecl *keyFunction = + CGM.getASTContext().getCurrentKeyFunction(RD); + if (!keyFunction) + return false; + + // Otherwise, if we don't have a definition of the key function, the + // vtable must be defined somewhere else. + return !keyFunction->hasBody(); +} + +static bool shouldEmitAvailableExternallyVTable(const CIRGenModule &CGM, + const CXXRecordDecl *RD) { + return CGM.getCodeGenOpts().OptimizationLevel > 0 && + CGM.getCXXABI().canSpeculativelyEmitVTable(RD); +} + +/// Given that we're currently at the end of the translation unit, and +/// we've emitted a reference to the vtable for this class, should +/// we define that vtable? +static bool shouldEmitVTableAtEndOfTranslationUnit(CIRGenModule &CGM, + const CXXRecordDecl *RD) { + // If vtable is internal then it has to be done. + if (!CGM.getVTables().isVTableExternal(RD)) + return true; + + // If it's external then maybe we will need it as available_externally. + return shouldEmitAvailableExternallyVTable(CGM, RD); +} + +/// Given that at some point we emitted a reference to one or more +/// vtables, and that we are now at the end of the translation unit, +/// decide whether we should emit them. +void CIRGenModule::buildDeferredVTables() { +#ifndef NDEBUG + // Remember the size of DeferredVTables, because we're going to assume + // that this entire operation doesn't modify it. + size_t savedSize = DeferredVTables.size(); +#endif + + for (const CXXRecordDecl *RD : DeferredVTables) + if (shouldEmitVTableAtEndOfTranslationUnit(*this, RD)) { + VTables.GenerateClassData(RD); + } else if (shouldOpportunisticallyEmitVTables()) { + llvm_unreachable("NYI"); + } + + assert(savedSize == DeferredVTables.size() && + "deferred extra vtables during vtable emission?"); + DeferredVTables.clear(); +} + +void CIRGenVTables::GenerateClassData(const CXXRecordDecl *RD) { + assert(!UnimplementedFeature::generateDebugInfo()); + + if (RD->getNumVBases()) + CGM.getCXXABI().emitVirtualInheritanceTables(RD); + + CGM.getCXXABI().emitVTableDefinitions(*this, RD); +} + +static void AddPointerLayoutOffset(CIRGenModule &CGM, + ConstantArrayBuilder &builder, + CharUnits offset) { + builder.add(mlir::cir::ConstPtrAttr::get(CGM.getBuilder().getContext(), + CGM.getBuilder().getUInt8PtrTy(), + offset.getQuantity())); +} + +static void AddRelativeLayoutOffset(CIRGenModule &CGM, + ConstantArrayBuilder &builder, + CharUnits offset) { + llvm_unreachable("NYI"); + // builder.add(llvm::ConstantInt::get(CGM.Int32Ty, offset.getQuantity())); +} + +void CIRGenVTables::addVTableComponent(ConstantArrayBuilder &builder, + const VTableLayout &layout, + unsigned componentIndex, + mlir::Attribute rtti, + unsigned &nextVTableThunkIndex, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage) { + auto &component = layout.vtable_components()[componentIndex]; + + auto addOffsetConstant = + useRelativeLayout() ? AddRelativeLayoutOffset : AddPointerLayoutOffset; + + switch (component.getKind()) { + case VTableComponent::CK_VCallOffset: + return addOffsetConstant(CGM, builder, component.getVCallOffset()); + + case VTableComponent::CK_VBaseOffset: + return addOffsetConstant(CGM, builder, component.getVBaseOffset()); + + case VTableComponent::CK_OffsetToTop: + return addOffsetConstant(CGM, builder, component.getOffsetToTop()); + + case VTableComponent::CK_RTTI: + if (useRelativeLayout()) { + llvm_unreachable("NYI"); + // return addRelativeComponent(builder, rtti, vtableAddressPoint, + // vtableHasLocalLinkage, + // /*isCompleteDtor=*/false); + } else { + assert(rtti.isa() && + "expected GlobalViewAttr"); + return builder.add(rtti); + } + + case VTableComponent::CK_FunctionPointer: + case VTableComponent::CK_CompleteDtorPointer: + case VTableComponent::CK_DeletingDtorPointer: { + GlobalDecl GD = component.getGlobalDecl(); + + if (CGM.getLangOpts().CUDA) { + llvm_unreachable("NYI"); + } + + [[maybe_unused]] auto getSpecialVirtualFn = + [&](StringRef name) -> mlir::Attribute { + // FIXME(PR43094): When merging comdat groups, lld can select a local + // symbol as the signature symbol even though it cannot be accessed + // outside that symbol's TU. The relative vtables ABI would make + // __cxa_pure_virtual and __cxa_deleted_virtual local symbols, and + // depending on link order, the comdat groups could resolve to the one + // with the local symbol. As a temporary solution, fill these components + // with zero. We shouldn't be calling these in the first place anyway. + if (useRelativeLayout()) + llvm_unreachable("NYI"); + + // For NVPTX devices in OpenMP emit special functon as null pointers, + // otherwise linking ends up with unresolved references. + if (CGM.getLangOpts().OpenMP && CGM.getLangOpts().OpenMPIsTargetDevice && + CGM.getTriple().isNVPTX()) + llvm_unreachable("NYI"); + + llvm_unreachable("NYI"); + // llvm::FunctionType *fnTy = + // llvm::FunctionType::get(CGM.VoidTy, /*isVarArg=*/false); + // llvm::Constant *fn = cast( + // CGM.CreateRuntimeFunction(fnTy, name).getCallee()); + // if (auto f = dyn_cast(fn)) + // f->setUnnamedAddr(llvm::GlobalValue::UnnamedAddr::Global); + // return llvm::ConstantExpr::getBitCast(fn, CGM.Int8PtrTy); + }; + + mlir::cir::FuncOp fnPtr; + // Pure virtual member functions. + if (cast(GD.getDecl())->isPure()) { + llvm_unreachable("NYI"); + // if (!PureVirtualFn) + // PureVirtualFn = + // getSpecialVirtualFn(CGM.getCXXABI().GetPureVirtualCallName()); + // fnPtr = PureVirtualFn; + + // Deleted virtual member functions. + } else if (cast(GD.getDecl())->isDeleted()) { + llvm_unreachable("NYI"); + // if (!DeletedVirtualFn) + // DeletedVirtualFn = + // getSpecialVirtualFn(CGM.getCXXABI().GetDeletedVirtualCallName()); + // fnPtr = DeletedVirtualFn; + + // Thunks. + } else if (nextVTableThunkIndex < layout.vtable_thunks().size() && + layout.vtable_thunks()[nextVTableThunkIndex].first == + componentIndex) { + llvm_unreachable("NYI"); + // auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; + + // nextVTableThunkIndex++; + // fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true); + + // Otherwise we can use the method definition directly. + } else { + auto fnTy = CGM.getTypes().GetFunctionTypeForVTable(GD); + fnPtr = CGM.GetAddrOfFunction(GD, fnTy, /*ForVTable=*/true); + } + + if (useRelativeLayout()) { + llvm_unreachable("NYI"); + } else { + return builder.add(mlir::cir::GlobalViewAttr::get( + CGM.getBuilder().getUInt8PtrTy(), + mlir::FlatSymbolRefAttr::get(fnPtr.getSymNameAttr()))); + } + } + + case VTableComponent::CK_UnusedFunctionPointer: + if (useRelativeLayout()) + llvm_unreachable("NYI"); + else { + llvm_unreachable("NYI"); + // return builder.addNullPointer(CGM.Int8PtrTy); + } + } + + llvm_unreachable("Unexpected vtable component kind"); +} + +void CIRGenVTables::createVTableInitializer(ConstantStructBuilder &builder, + const VTableLayout &layout, + mlir::Attribute rtti, + bool vtableHasLocalLinkage) { + auto componentType = getVTableComponentType(); + + const auto &addressPoints = layout.getAddressPointIndices(); + unsigned nextVTableThunkIndex = 0; + for (unsigned vtableIndex = 0, endIndex = layout.getNumVTables(); + vtableIndex != endIndex; ++vtableIndex) { + auto vtableElem = builder.beginArray(componentType); + + size_t vtableStart = layout.getVTableOffset(vtableIndex); + size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex); + for (size_t componentIndex = vtableStart; componentIndex < vtableEnd; + ++componentIndex) { + addVTableComponent(vtableElem, layout, componentIndex, rtti, + nextVTableThunkIndex, addressPoints[vtableIndex], + vtableHasLocalLinkage); + } + vtableElem.finishAndAddTo(rtti.getContext(), builder); + } +} + +/// Compute the required linkage of the vtable for the given class. +/// +/// Note that we only call this at the end of the translation unit. +mlir::cir::GlobalLinkageKind +CIRGenModule::getVTableLinkage(const CXXRecordDecl *RD) { + if (!RD->isExternallyVisible()) + return mlir::cir::GlobalLinkageKind::InternalLinkage; + + // We're at the end of the translation unit, so the current key + // function is fully correct. + const CXXMethodDecl *keyFunction = astCtx.getCurrentKeyFunction(RD); + if (keyFunction && !RD->hasAttr()) { + // If this class has a key function, use that to determine the + // linkage of the vtable. + const FunctionDecl *def = nullptr; + if (keyFunction->hasBody(def)) + keyFunction = cast(def); + + switch (keyFunction->getTemplateSpecializationKind()) { + case TSK_Undeclared: + case TSK_ExplicitSpecialization: + assert( + (def || codeGenOpts.OptimizationLevel > 0 || + codeGenOpts.getDebugInfo() != llvm::codegenoptions::NoDebugInfo) && + "Shouldn't query vtable linkage without key function, " + "optimizations, or debug info"); + if (!def && codeGenOpts.OptimizationLevel > 0) + return mlir::cir::GlobalLinkageKind::AvailableExternallyLinkage; + + if (keyFunction->isInlined()) + return !astCtx.getLangOpts().AppleKext + ? mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage + : mlir::cir::GlobalLinkageKind::InternalLinkage; + + return mlir::cir::GlobalLinkageKind::ExternalLinkage; + + case TSK_ImplicitInstantiation: + return !astCtx.getLangOpts().AppleKext + ? mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage + : mlir::cir::GlobalLinkageKind::InternalLinkage; + + case TSK_ExplicitInstantiationDefinition: + return !astCtx.getLangOpts().AppleKext + ? mlir::cir::GlobalLinkageKind::WeakODRLinkage + : mlir::cir::GlobalLinkageKind::InternalLinkage; + + case TSK_ExplicitInstantiationDeclaration: + llvm_unreachable("Should not have been asked to emit this"); + } + } + + // -fapple-kext mode does not support weak linkage, so we must use + // internal linkage. + if (astCtx.getLangOpts().AppleKext) + return mlir::cir::GlobalLinkageKind::InternalLinkage; + + auto DiscardableODRLinkage = mlir::cir::GlobalLinkageKind::LinkOnceODRLinkage; + auto NonDiscardableODRLinkage = mlir::cir::GlobalLinkageKind::WeakODRLinkage; + if (RD->hasAttr()) { + // Cannot discard exported vtables. + DiscardableODRLinkage = NonDiscardableODRLinkage; + } else if (RD->hasAttr()) { + // Imported vtables are available externally. + DiscardableODRLinkage = + mlir::cir::GlobalLinkageKind::AvailableExternallyLinkage; + NonDiscardableODRLinkage = + mlir::cir::GlobalLinkageKind::AvailableExternallyLinkage; + } + + switch (RD->getTemplateSpecializationKind()) { + case TSK_Undeclared: + case TSK_ExplicitSpecialization: + case TSK_ImplicitInstantiation: + return DiscardableODRLinkage; + + case TSK_ExplicitInstantiationDeclaration: { + // Explicit instantiations in MSVC do not provide vtables, so we must emit + // our own. + if (getTarget().getCXXABI().isMicrosoft()) + return DiscardableODRLinkage; + auto r = shouldEmitAvailableExternallyVTable(*this, RD) + ? mlir::cir::GlobalLinkageKind::AvailableExternallyLinkage + : mlir::cir::GlobalLinkageKind::ExternalLinkage; + assert(r == mlir::cir::GlobalLinkageKind::ExternalLinkage && + "available external NYI"); + return r; + } + + case TSK_ExplicitInstantiationDefinition: + return NonDiscardableODRLinkage; + } + + llvm_unreachable("Invalid TemplateSpecializationKind!"); +} + +mlir::cir::GlobalOp +getAddrOfVTTVTable(CIRGenVTables &CGVT, CIRGenModule &CGM, + const CXXRecordDecl *MostDerivedClass, + const VTTVTable &vtable, + mlir::cir::GlobalLinkageKind linkage, + VTableLayout::AddressPointsMapTy &addressPoints) { + if (vtable.getBase() == MostDerivedClass) { + assert(vtable.getBaseOffset().isZero() && + "Most derived class vtable must have a zero offset!"); + // This is a regular vtable. + return CGM.getCXXABI().getAddrOfVTable(MostDerivedClass, CharUnits()); + } + + llvm_unreachable("generateConstructionVTable NYI"); +} + +mlir::cir::GlobalOp CIRGenVTables::getAddrOfVTT(const CXXRecordDecl *RD) +{ + assert(RD->getNumVBases() && "Only classes with virtual bases need a VTT"); + + SmallString<256> OutName; + llvm::raw_svector_ostream Out(OutName); + cast(CGM.getCXXABI().getMangleContext()) + .mangleCXXVTT(RD, Out); + StringRef Name = OutName.str(); + + // This will also defer the definition of the VTT. + (void)CGM.getCXXABI().getAddrOfVTable(RD, CharUnits()); + + VTTBuilder Builder(CGM.getASTContext(), RD, /*GenerateDefinition=*/false); + + auto ArrayType = mlir::cir::ArrayType::get(CGM.getBuilder().getContext(), + CGM.getBuilder().getUInt8PtrTy(), + Builder.getVTTComponents().size()); + auto Align = + CGM.getDataLayout().getABITypeAlign(CGM.getBuilder().getUInt8PtrTy()); + auto VTT = CGM.createOrReplaceCXXRuntimeVariable( + CGM.getLoc(RD->getSourceRange()), Name, ArrayType, + mlir::cir::GlobalLinkageKind::ExternalLinkage, + CharUnits::fromQuantity(Align)); + CGM.setGVProperties(VTT, RD); + return VTT; +} + +/// Emit the definition of the given vtable. +void CIRGenVTables::buildVTTDefinition(mlir::cir::GlobalOp VTT, + mlir::cir::GlobalLinkageKind Linkage, + const CXXRecordDecl *RD) { + VTTBuilder Builder(CGM.getASTContext(), RD, /*GenerateDefinition=*/true); + + auto ArrayType = mlir::cir::ArrayType::get(CGM.getBuilder().getContext(), + CGM.getBuilder().getUInt8PtrTy(), + Builder.getVTTComponents().size()); + + SmallVector VTables; + SmallVector VTableAddressPoints; + for (const VTTVTable *i = Builder.getVTTVTables().begin(), + *e = Builder.getVTTVTables().end(); + i != e; ++i) { + VTableAddressPoints.push_back(VTableAddressPointsMapTy()); + VTables.push_back(getAddrOfVTTVTable(*this, CGM, RD, *i, Linkage, + VTableAddressPoints.back())); + } + + SmallVector VTTComponents; + for (const VTTComponent *i = Builder.getVTTComponents().begin(), + *e = Builder.getVTTComponents().end(); + i != e; ++i) { + const VTTVTable &VTTVT = Builder.getVTTVTables()[i->VTableIndex]; + mlir::cir::GlobalOp VTable = VTables[i->VTableIndex]; + VTableLayout::AddressPointLocation AddressPoint; + if (VTTVT.getBase() == RD) { + // Just get the address point for the regular vtable. + AddressPoint = + getItaniumVTableContext().getVTableLayout(RD).getAddressPoint( + i->VTableBase); + } else { + AddressPoint = VTableAddressPoints[i->VTableIndex].lookup(i->VTableBase); + assert(AddressPoint.AddressPointIndex != 0 && + "Did not find ctor vtable address point!"); + } + + mlir::Attribute Idxs[3] = { + mlir::cir::IntAttr::get(CGM.getBuilder().getSInt32Ty(), 0), + mlir::cir::IntAttr::get(CGM.getBuilder().getSInt32Ty(), + AddressPoint.VTableIndex), + mlir::cir::IntAttr::get(CGM.getBuilder().getSInt32Ty(), + AddressPoint.AddressPointIndex), + }; + + auto Indices = mlir::ArrayAttr::get(CGM.getBuilder().getContext(), Idxs); + auto Init = CGM.getBuilder().getGlobalViewAttr( + CGM.getBuilder().getUInt8PtrTy(), VTable, Indices); + + VTTComponents.push_back(Init); + } + + auto Init = CGM.getBuilder().getConstArray( + mlir::ArrayAttr::get(CGM.getBuilder().getContext(), VTTComponents), + ArrayType); + + VTT.setInitialValueAttr(Init); + + // Set the correct linkage. + VTT.setLinkage(Linkage); + mlir::SymbolTable::setSymbolVisibility(VTT, + CIRGenModule::getMLIRVisibility(VTT)); + + if (CGM.supportsCOMDAT() && VTT.isWeakForLinker()) { + assert(!UnimplementedFeature::setComdat()); + } +} + +void CIRGenVTables::buildThunks(GlobalDecl GD) { + const CXXMethodDecl *MD = + cast(GD.getDecl())->getCanonicalDecl(); + + // We don't need to generate thunks for the base destructor. + if (isa(MD) && GD.getDtorType() == Dtor_Base) + return; + + const VTableContextBase::ThunkInfoVectorTy *ThunkInfoVector = + VTContext->getThunkInfo(GD); + + if (!ThunkInfoVector) + return; + + for ([[maybe_unused]] const ThunkInfo &Thunk : *ThunkInfoVector) + llvm_unreachable("NYI"); +} + +bool CIRGenModule::AlwaysHasLTOVisibilityPublic(const CXXRecordDecl *RD) { + if (RD->hasAttr() || RD->hasAttr() || + RD->hasAttr() || RD->hasAttr()) + return true; + + if (!getCodeGenOpts().LTOVisibilityPublicStd) + return false; + + const DeclContext *DC = RD; + while (true) { + auto *D = cast(DC); + DC = DC->getParent(); + if (isa(DC->getRedeclContext())) { + if (auto *ND = dyn_cast(D)) + if (const IdentifierInfo *II = ND->getIdentifier()) + if (II->isStr("std") || II->isStr("stdext")) + return true; + break; + } + } + + return false; +} + +bool CIRGenModule::HasHiddenLTOVisibility(const CXXRecordDecl *RD) { + LinkageInfo LV = RD->getLinkageAndVisibility(); + if (!isExternallyVisible(LV.getLinkage())) + return true; + + if (!getTriple().isOSBinFormatCOFF() && + LV.getVisibility() != HiddenVisibility) + return false; + + return !AlwaysHasLTOVisibilityPublic(RD); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.h b/clang/lib/CIR/CodeGen/CIRGenVTables.h new file mode 100644 index 000000000000..e92f60394270 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.h @@ -0,0 +1,178 @@ +//===--- CIRGenVTables.h - Emit LLVM Code for C++ vtables -------*- C++ -*-===// +// +// 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 dealing with C++ code generation of virtual tables. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_CODEGEN_CIRGENVTABLES_H +#define LLVM_CLANG_LIB_CIR_CODEGEN_CIRGENVTABLES_H + +#include "ConstantInitBuilder.h" +#include "clang/AST/BaseSubobject.h" +#include "clang/AST/CharUnits.h" +#include "clang/AST/GlobalDecl.h" +#include "clang/AST/VTableBuilder.h" +#include "clang/Basic/ABI.h" +#include "llvm/ADT/DenseMap.h" + +namespace clang { +class CXXRecordDecl; +} + +namespace cir { +class CIRGenModule; +// class ConstantArrayBuilder; +// class ConstantStructBuilder; + +class CIRGenVTables { + CIRGenModule &CGM; + + clang::VTableContextBase *VTContext; + + /// VTableAddressPointsMapTy - Address points for a single vtable. + typedef clang::VTableLayout::AddressPointsMapTy VTableAddressPointsMapTy; + + typedef std::pair + BaseSubobjectPairTy; + typedef llvm::DenseMap SubVTTIndiciesMapTy; + + /// SubVTTIndicies - Contains indices into the various sub-VTTs. + SubVTTIndiciesMapTy SubVTTIndicies; + + typedef llvm::DenseMap + SecondaryVirtualPointerIndicesMapTy; + + /// SecondaryVirtualPointerIndices - Contains the secondary virtual pointer + /// indices. + SecondaryVirtualPointerIndicesMapTy SecondaryVirtualPointerIndices; + + // /// Cache for the pure virtual member call function. + // llvm::Constant *PureVirtualFn = nullptr; + + // /// Cache for the deleted virtual member call function. + // llvm::Constant *DeletedVirtualFn = nullptr; + + // /// Get the address of a thunk and emit it if necessary. + // llvm::Constant *maybeEmitThunk(GlobalDecl GD, + // const ThunkInfo &ThunkAdjustments, + // bool ForVTable); + + void addVTableComponent(ConstantArrayBuilder &builder, + const VTableLayout &layout, unsigned componentIndex, + mlir::Attribute rtti, unsigned &nextVTableThunkIndex, + unsigned vtableAddressPoint, + bool vtableHasLocalLinkage); + + // /// Add a 32-bit offset to a component relative to the vtable when using + // the + // /// relative vtables ABI. The array builder points to the start of the + // vtable. void addRelativeComponent(ConstantArrayBuilder &builder, + // llvm::Constant *component, + // unsigned vtableAddressPoint, + // bool vtableHasLocalLinkage, + // bool isCompleteDtor) const; + + // /// Create a dso_local stub that will be used for a relative reference in + // the + // /// relative vtable layout. This stub will just be a tail call to the + // original + // /// function and propagate any function attributes from the original. If + // the + // /// original function is already dso_local, the original is returned + // instead + // /// and a stub is not created. + // llvm::Function * + // getOrCreateRelativeStub(llvm::Function *func, + // llvm::GlobalValue::LinkageTypes stubLinkage, + // bool isCompleteDtor) const; + + bool useRelativeLayout() const; + + mlir::Type getVTableComponentType(); + +public: + /// Add vtable components for the given vtable layout to the given + /// global initializer. + void createVTableInitializer(ConstantStructBuilder &builder, + const VTableLayout &layout, mlir::Attribute rtti, + bool vtableHasLocalLinkage); + + CIRGenVTables(CIRGenModule &CGM); + + clang::ItaniumVTableContext &getItaniumVTableContext() { + return *llvm::cast(VTContext); + } + + const clang::ItaniumVTableContext &getItaniumVTableContext() const { + return *llvm::cast(VTContext); + } + + // MicrosoftVTableContext &getMicrosoftVTableContext() { + // return *cast(VTContext); + // } + + // /// getSubVTTIndex - Return the index of the sub-VTT for the base class + // of the + // /// given record decl. + // uint64_t getSubVTTIndex(const CXXRecordDecl *RD, BaseSubobject Base); + + // /// getSecondaryVirtualPointerIndex - Return the index in the VTT where + // the + // /// virtual pointer for the given subobject is located. + // uint64_t getSecondaryVirtualPointerIndex(const CXXRecordDecl *RD, + // BaseSubobject Base); + + // /// GenerateConstructionVTable - Generate a construction vtable for the + // given + // /// base subobject. + // llvm::GlobalVariable * + // GenerateConstructionVTable(const CXXRecordDecl *RD, const BaseSubobject + // &Base, + // bool BaseIsVirtual, + // llvm::GlobalVariable::LinkageTypes Linkage, + // VTableAddressPointsMapTy &AddressPoints); + + /// Get the address of the VTT for the given record decl. + mlir::cir::GlobalOp getAddrOfVTT(const CXXRecordDecl *RD); + + /// Emit the definition of the given vtable. + void buildVTTDefinition(mlir::cir::GlobalOp VTT, + mlir::cir::GlobalLinkageKind Linkage, + const CXXRecordDecl *RD); + + /// Emit the associated thunks for the given global decl. + void buildThunks(GlobalDecl GD); + + /// Generate all the class data required to be generated upon definition of a + /// KeyFunction. This includes the vtable, the RTTI data structure (if RTTI + /// is enabled) and the VTT (if the class has virtual bases). + void GenerateClassData(const clang::CXXRecordDecl *RD); + + bool isVTableExternal(const clang::CXXRecordDecl *RD); + + /// Returns the type of a vtable with the given layout. Normally a struct of + /// arrays of pointers, with one struct element for each vtable in the vtable + /// group. + mlir::Type getVTableType(const clang::VTableLayout &layout); + + // /// Generate a public facing alias for the vtable and make the vtable + // either + // /// hidden or private. The alias will have the original linkage and + // visibility + // /// of the vtable. This is used for cases under the relative vtables ABI + // /// when a vtable may not be dso_local. + // void GenerateRelativeVTableAlias(llvm::GlobalVariable *VTable, + // llvm::StringRef AliasNameRef); + + // /// Specify a global should not be instrumented with hwasan. + // void RemoveHwasanMetadata(llvm::GlobalValue *GV) const; +}; + +} // end namespace cir +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenValue.h b/clang/lib/CIR/CodeGen/CIRGenValue.h new file mode 100644 index 000000000000..c6edeb4d4fe4 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenValue.h @@ -0,0 +1,487 @@ +//===-- CIRGenValue.h - CIRGen wrappers for mlir::Value ---------*- C++ -*-===// +// +// 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 LLVM_CLANG_LIB_CIR_CIRGENVALUE_H +#define LLVM_CLANG_LIB_CIR_CIRGENVALUE_H + +#include "Address.h" +#include "CIRGenRecordLayout.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/CharUnits.h" +#include "clang/AST/Type.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "llvm/ADT/PointerIntPair.h" + +#include "mlir/IR/Value.h" + +namespace cir { + +/// 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 }; + + // The shift to make to an aggregate's alignment to make it look + // like a pointer. + enum { AggAlignShift = 4 }; + + // Stores first value and flavor. + llvm::PointerIntPair V1; + // Stores second value and volatility. + llvm::PointerIntPair, 1, bool> V2; + // Stores element type for aggregate values. + mlir::Type ElementType; + +public: + bool isScalar() const { return V1.getInt() == Scalar; } + bool isComplex() const { return V1.getInt() == Complex; } + bool isAggregate() const { return V1.getInt() == Aggregate; } + bool isIgnored() const { return isScalar() && !getScalarVal(); } + + bool isVolatileQualified() const { return V2.getInt(); } + + /// Return the mlir::Value of this scalar value. + mlir::Value getScalarVal() const { + assert(isScalar() && "Not a scalar!"); + return V1.getPointer(); + } + + /// Return the real/imag components of this complex value. + std::pair getComplexVal() const { + assert(0 && "not implemented"); + return {}; + } + + /// Return the mlir::Value of the address of the aggregate. + Address getAggregateAddress() const { + assert(isAggregate() && "Not an aggregate!"); + auto align = reinterpret_cast(V2.getPointer().get()) >> + AggAlignShift; + return Address(V1.getPointer(), ElementType, + clang::CharUnits::fromQuantity(align)); + } + + mlir::Value getAggregatePointer() const { + assert(isAggregate() && "Not an aggregate!"); + return V1.getPointer(); + } + + static RValue getIgnored() { + // FIXME: should we make this a more explicit state? + return get(nullptr); + } + + static RValue get(mlir::Value V) { + RValue ER; + ER.V1.setPointer(V); + ER.V1.setInt(Scalar); + ER.V2.setInt(false); + return ER; + } + static RValue getComplex(mlir::Value V1, mlir::Value V2) { + assert(0 && "not implemented"); + return RValue{}; + } + static RValue getComplex(const std::pair &C) { + assert(0 && "not implemented"); + return RValue{}; + } + // FIXME: Aggregate rvalues need to retain information about whether they are + // volatile or not. Remove default to find all places that probably get this + // wrong. + static RValue getAggregate(Address addr, bool isVolatile = false) { + RValue ER; + ER.V1.setPointer(addr.getPointer()); + ER.V1.setInt(Aggregate); + ER.ElementType = addr.getElementType(); + + auto align = static_cast(addr.getAlignment().getQuantity()); + ER.V2.setPointer(reinterpret_cast(align << AggAlignShift)); + ER.V2.setInt(isVolatile); + 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 +}; + +/// Given that the base address has the given alignment source, what's +/// our confidence in the alignment of the field? +static inline AlignmentSource getFieldAlignmentSource(AlignmentSource Source) { + // For now, we don't distinguish fields of opaque pointers from + // top-level declarations, but maybe we should. + return AlignmentSource::Decl; +} + +class LValueBaseInfo { + AlignmentSource AlignSource; + +public: + explicit LValueBaseInfo(AlignmentSource Source = AlignmentSource::Type) + : AlignSource(Source) {} + AlignmentSource getAlignmentSource() const { return AlignSource; } + void setAlignmentSource(AlignmentSource Source) { AlignSource = Source; } + + void mergeForCast(const LValueBaseInfo &Info) { + setAlignmentSource(Info.getAlignmentSource()); + } +}; + +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; + clang::Qualifiers Quals; + + // LValue is non-gc'able for any reason, including being a parameter or local + // variable. + bool NonGC : 1; + + // This flag shows if a nontemporal load/stores should be used when accessing + // this lvalue. + bool Nontemporal : 1; + +private: + void Initialize(clang::QualType Type, clang::Qualifiers Quals, + clang::CharUnits Alignment, LValueBaseInfo BaseInfo) { + assert((!Alignment.isZero() || Type->isIncompleteType()) && + "initializing l-value with zero alignment!"); + if (isGlobalReg()) + assert(ElementType == nullptr && "Global reg does not store elem type"); + + this->Type = Type; + this->Quals = Quals; + // This flag shows if a nontemporal load/stores should be used when + // accessing this lvalue. + const unsigned MaxAlign = 1U << 31; + this->Alignment = Alignment.getQuantity() <= MaxAlign + ? Alignment.getQuantity() + : MaxAlign; + assert(this->Alignment == Alignment.getQuantity() && + "Alignment exceeds allowed max!"); + this->BaseInfo = BaseInfo; + + // TODO: ObjC flags + // Initialize Objective-C flags. + this->NonGC = false; + this->Nontemporal = false; + } + + // The alignment to use when accessing this lvalue. (For vector elements, + // this is the alignment of the whole vector) + unsigned Alignment; + mlir::Value V; + mlir::Type ElementType; + LValueBaseInfo BaseInfo; + const CIRGenBitFieldInfo *BitFieldInfo{0}; + +public: + bool isSimple() const { return LVType == Simple; } + bool isVectorElt() const { return LVType == VectorElt; } + bool isBitField() const { return LVType == BitField; } + bool isExtVectorElt() const { return LVType == ExtVectorElt; } + bool isGlobalReg() const { return LVType == GlobalReg; } + bool isMatrixElt() const { return LVType == MatrixElt; } + + bool isVolatileQualified() const { return Quals.hasVolatile(); } + + unsigned getVRQualifiers() const { + return Quals.getCVRQualifiers() & ~clang::Qualifiers::Const; + } + + bool isNonGC() const { return NonGC; } + void setNonGC(bool Value) { NonGC = Value; } + + bool isNontemporal() const { return Nontemporal; } + + bool isObjCWeak() const { + return Quals.getObjCGCAttr() == clang::Qualifiers::Weak; + } + bool isObjCStrong() const { + return Quals.getObjCGCAttr() == clang::Qualifiers::Strong; + } + + bool isVolatile() const { return Quals.hasVolatile(); } + + clang::QualType getType() const { return Type; } + + mlir::Value getPointer() const { return V; } + + clang::CharUnits getAlignment() const { + return clang::CharUnits::fromQuantity(Alignment); + } + + Address getAddress() const { + return Address(getPointer(), ElementType, getAlignment()); + } + + void setAddress(Address address) { + assert(isSimple()); + V = address.getPointer(); + ElementType = address.getElementType(); + Alignment = address.getAlignment().getQuantity(); + // TODO(cir): IsKnownNonNull = address.isKnownNonNull(); + } + + LValueBaseInfo getBaseInfo() const { return BaseInfo; } + void setBaseInfo(LValueBaseInfo Info) { BaseInfo = Info; } + + static LValue makeAddr(Address address, clang::QualType T, + AlignmentSource Source = AlignmentSource::Type) { + LValue R; + R.LVType = Simple; + R.V = address.getPointer(); + R.ElementType = address.getElementType(); + R.Initialize(T, T.getQualifiers(), address.getAlignment(), + LValueBaseInfo(Source)); + return R; + } + + // FIXME: only have one of these static methods. + static LValue makeAddr(Address address, clang::QualType T, + LValueBaseInfo LBI) { + LValue R; + R.LVType = Simple; + R.V = address.getPointer(); + R.ElementType = address.getElementType(); + R.Initialize(T, T.getQualifiers(), address.getAlignment(), LBI); + return R; + } + + static LValue makeAddr(Address address, clang::QualType type, + clang::ASTContext &Context, LValueBaseInfo BaseInfo) { + clang::Qualifiers qs = type.getQualifiers(); + qs.setObjCGCAttr(Context.getObjCGCAttrKind(type)); + + LValue R; + R.LVType = Simple; + assert(address.getPointer().getType().cast()); + R.V = address.getPointer(); + R.ElementType = address.getElementType(); + R.Initialize(type, qs, address.getAlignment(), + BaseInfo); // TODO: TBAAInfo); + return R; + } + + const clang::Qualifiers &getQuals() const { return Quals; } + clang::Qualifiers &getQuals() { return Quals; } + + // bitfield lvalue + Address getBitFieldAddress() const { + return Address(getBitFieldPointer(), ElementType, getAlignment()); + } + + mlir::Value getBitFieldPointer() const { + assert(isBitField()); + return V; + } + + const CIRGenBitFieldInfo &getBitFieldInfo() const { + assert(isBitField()); + return *BitFieldInfo; + } + + /// Create a new object to represent a bit-field access. + /// + /// \param Addr - The base address of the bit-field sequence this + /// bit-field refers to. + /// \param Info - The information describing how to perform the bit-field + /// access. + static LValue MakeBitfield(Address Addr, const CIRGenBitFieldInfo &Info, + clang::QualType type, LValueBaseInfo BaseInfo) { + LValue R; + R.LVType = BitField; + R.V = Addr.getPointer(); + R.ElementType = Addr.getElementType(); + R.BitFieldInfo = &Info; + R.Initialize(type, type.getQualifiers(), Addr.getAlignment(), BaseInfo); + return R; + } +}; + +/// An aggregate value slot. +class AggValueSlot { + /// The address. + Address Addr; + + // Qualifiers + clang::Qualifiers Quals; + + /// This is set to true if some external code is responsible for setting up a + /// destructor for the slot. Otherwise the code which constructs it should + /// push the appropriate cleanup. + bool DestructedFlag : 1; + + /// This is set to true if writing to the memory in the slot might require + /// calling an appropriate Objective-C GC barrier. The exact interaction here + /// is unnecessarily mysterious. + bool ObjCGCFlag : 1; + + /// This is set to true if the memory in the slot is known to be zero before + /// the assignment into it. This means that zero fields don't need to be set. + bool ZeroedFlag : 1; + + /// This is set to true if the slot might be aliased and it's not undefined + /// behavior to access it through such an alias. Note that it's always + /// undefined behavior to access a C++ object that's under construction + /// through an alias derived from outside the construction process. + /// + /// This flag controls whether calls that produce the aggregate + /// value may be evaluated directly into the slot, or whether they + /// must be evaluated into an unaliased temporary and then memcpy'ed + /// over. Since it's invalid in general to memcpy a non-POD C++ + /// object, it's important that this flag never be set when + /// evaluating an expression which constructs such an object. + bool AliasedFlag : 1; + + /// This is set to true if the tail padding of this slot might overlap + /// another object that may have already been initialized (and whose + /// value must be preserved by this initialization). If so, we may only + /// store up to the dsize of the type. Otherwise we can widen stores to + /// the size of the type. + bool OverlapFlag : 1; + + /// If is set to true, sanitizer checks are already generated for this address + /// or not required. For instance, if this address represents an object + /// created in 'new' expression, sanitizer checks for memory is made as a part + /// of 'operator new' emission and object constructor should not generate + /// them. + bool SanitizerCheckedFlag : 1; + + AggValueSlot(Address Addr, clang::Qualifiers Quals, bool DestructedFlag, + bool ObjCGCFlag, bool ZeroedFlag, bool AliasedFlag, + bool OverlapFlag, bool SanitizerCheckedFlag) + : Addr(Addr), Quals(Quals), DestructedFlag(DestructedFlag), + ObjCGCFlag(ObjCGCFlag), ZeroedFlag(ZeroedFlag), + AliasedFlag(AliasedFlag), OverlapFlag(OverlapFlag), + SanitizerCheckedFlag(SanitizerCheckedFlag) {} + +public: + enum IsAliased_t { IsNotAliased, IsAliased }; + enum IsDestructed_t { IsNotDestructed, IsDestructed }; + enum IsZeroed_t { IsNotZeroed, IsZeroed }; + enum Overlap_t { DoesNotOverlap, MayOverlap }; + enum NeedsGCBarriers_t { DoesNotNeedGCBarriers, NeedsGCBarriers }; + enum IsSanitizerChecked_t { IsNotSanitizerChecked, IsSanitizerChecked }; + + /// ignored - Returns an aggregate value slot indicating that the aggregate + /// value is being ignored. + static AggValueSlot ignored() { + return forAddr(Address::invalid(), clang::Qualifiers(), IsNotDestructed, + DoesNotNeedGCBarriers, IsNotAliased, DoesNotOverlap); + } + + /// forAddr - Make a slot for an aggregate value. + /// + /// \param quals - The qualifiers that dictate how the slot should be + /// initialized. Only 'volatile' and the Objective-C lifetime qualifiers + /// matter. + /// + /// \param isDestructed - true if something else is responsible for calling + /// destructors on this object + /// \param needsGC - true fi the slot is potentially located somewhere that + /// ObjC GC calls should be emitted for + static AggValueSlot + forAddr(Address addr, clang::Qualifiers quals, IsDestructed_t isDestructed, + NeedsGCBarriers_t needsGC, IsAliased_t isAliased, + Overlap_t mayOverlap, IsZeroed_t isZeroed = IsNotZeroed, + IsSanitizerChecked_t isChecked = IsNotSanitizerChecked) { + return AggValueSlot(addr, quals, isDestructed, needsGC, isZeroed, isAliased, + mayOverlap, isChecked); + } + + static AggValueSlot + forLValue(const LValue &LV, IsDestructed_t isDestructed, + NeedsGCBarriers_t needsGC, IsAliased_t isAliased, + Overlap_t mayOverlap, IsZeroed_t isZeroed = IsNotZeroed, + IsSanitizerChecked_t isChecked = IsNotSanitizerChecked) { + return forAddr(LV.getAddress(), LV.getQuals(), isDestructed, needsGC, + isAliased, mayOverlap, isZeroed, isChecked); + } + + IsDestructed_t isExternallyDestructed() const { + return IsDestructed_t(DestructedFlag); + } + void setExternallyDestructed(bool destructed = true) { + DestructedFlag = destructed; + } + + clang::Qualifiers getQualifiers() const { return Quals; } + + bool isVolatile() const { return Quals.hasVolatile(); } + + Address getAddress() const { return Addr; } + + bool isIgnored() const { return !Addr.isValid(); } + + mlir::Value getPointer() const { return Addr.getPointer(); } + + Overlap_t mayOverlap() const { return Overlap_t(OverlapFlag); } + + bool isSanitizerChecked() const { return SanitizerCheckedFlag; } + + IsZeroed_t isZeroed() const { return IsZeroed_t(ZeroedFlag); } + void setZeroed(bool V = true) { ZeroedFlag = V; } + + NeedsGCBarriers_t requiresGCollection() const { + return NeedsGCBarriers_t(ObjCGCFlag); + } + + IsAliased_t isPotentiallyAliased() const { return IsAliased_t(AliasedFlag); } + + RValue asRValue() const { + if (isIgnored()) { + return RValue::getIgnored(); + } else { + return RValue::getAggregate(getAddress(), isVolatile()); + } + } + + /// Get the preferred size to use when storing a value to this slot. This + /// is the type size unless that might overlap another object, in which + /// case it's the dsize. + clang::CharUnits getPreferredSize(clang::ASTContext &Ctx, + clang::QualType Type) { + return mayOverlap() ? Ctx.getTypeInfoDataSizeInChars(Type).Width + : Ctx.getTypeSizeInChars(Type); + } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/CIRGenerator.cpp b/clang/lib/CIR/CodeGen/CIRGenerator.cpp new file mode 100644 index 000000000000..4fe46c923dda --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRGenerator.cpp @@ -0,0 +1,190 @@ +//===--- CIRGenerator.cpp - Emit CIR from ASTs ----------------------------===// +// +// 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 builds an AST and converts it to CIR. +// +//===----------------------------------------------------------------------===// + +#include "CIRGenModule.h" + +#include "mlir/Dialect/DLTI/DLTI.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMDialect.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/Target/LLVMIR/Import.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/CIR/CIRGenerator.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +using namespace cir; +using namespace clang; + +void CIRGenerator::anchor() {} + +CIRGenerator::CIRGenerator(clang::DiagnosticsEngine &diags, + llvm::IntrusiveRefCntPtr vfs, + const CodeGenOptions &CGO) + : Diags(diags), fs(std::move(vfs)), codeGenOpts{CGO}, + HandlingTopLevelDecls(0) {} +CIRGenerator::~CIRGenerator() { + // There should normally not be any leftover inline method definitions. + assert(DeferredInlineMemberFuncDefs.empty() || Diags.hasErrorOccurred()); +} + +static void setMLIRDataLayout(mlir::ModuleOp &mod, const llvm::DataLayout &dl) { + auto *context = mod.getContext(); + mod->setAttr(mlir::LLVM::LLVMDialect::getDataLayoutAttrName(), + mlir::StringAttr::get(context, dl.getStringRepresentation())); + mlir::DataLayoutSpecInterface dlSpec = mlir::translateDataLayout(dl, context); + mod->setAttr(mlir::DLTIDialect::kDataLayoutAttrName, dlSpec); +} + +void CIRGenerator::Initialize(ASTContext &astCtx) { + using namespace llvm; + + this->astCtx = &astCtx; + + mlirCtx = std::make_unique(); + mlirCtx->getOrLoadDialect(); + mlirCtx->getOrLoadDialect(); + mlirCtx->getOrLoadDialect(); + mlirCtx->getOrLoadDialect(); + mlirCtx->getOrLoadDialect(); + CGM = std::make_unique(*mlirCtx.get(), astCtx, codeGenOpts, + Diags); + auto mod = CGM->getModule(); + auto layout = llvm::DataLayout(astCtx.getTargetInfo().getDataLayoutString()); + setMLIRDataLayout(mod, layout); +} + +bool CIRGenerator::verifyModule() { return CGM->verifyModule(); } + +bool CIRGenerator::EmitFunction(const FunctionDecl *FD) { + llvm_unreachable("NYI"); +} + +mlir::ModuleOp CIRGenerator::getModule() { return CGM->getModule(); } + +bool CIRGenerator::HandleTopLevelDecl(DeclGroupRef D) { + if (Diags.hasErrorOccurred()) + return true; + + HandlingTopLevelDeclRAII HandlingDecl(*this); + + for (DeclGroupRef::iterator I = D.begin(), E = D.end(); I != E; ++I) { + CGM->buildTopLevelDecl(*I); + } + + return true; +} + +void CIRGenerator::HandleTranslationUnit(ASTContext &C) { + // Release the Builder when there is no error. + if (!Diags.hasErrorOccurred() && CGM) + CGM->Release(); + + // If there are errors before or when releasing the CGM, reset the module to + // stop here before invoking the backend. + if (Diags.hasErrorOccurred()) { + if (CGM) + // TODO: CGM->clear(); + // TODO: M.reset(); + return; + } +} + +void CIRGenerator::HandleInlineFunctionDefinition(FunctionDecl *D) { + if (Diags.hasErrorOccurred()) + return; + + assert(D->doesThisDeclarationHaveABody()); + + // We may want to emit this definition. However, that decision might be + // based on computing the linkage, and we have to defer that in case we are + // inside of something that will chagne the method's final linkage, e.g. + // typedef struct { + // void bar(); + // void foo() { bar(); } + // } A; + DeferredInlineMemberFuncDefs.push_back(D); + + // Provide some coverage mapping even for methods that aren't emitted. + // Don't do this for templated classes though, as they may not be + // instantiable. + if (!D->getLexicalDeclContext()->isDependentContext()) + CGM->AddDeferredUnusedCoverageMapping(D); +} + +void CIRGenerator::buildDefaultMethods() { CGM->buildDefaultMethods(); } + +void CIRGenerator::buildDeferredDecls() { + if (DeferredInlineMemberFuncDefs.empty()) + return; + + // Emit any deferred inline method definitions. Note that more deferred + // methods may be added during this loop, since ASTConsumer callbacks can be + // invoked if AST inspection results in declarations being added. + HandlingTopLevelDeclRAII HandlingDecls(*this); + for (unsigned I = 0; I != DeferredInlineMemberFuncDefs.size(); ++I) + CGM->buildTopLevelDecl(DeferredInlineMemberFuncDefs[I]); + DeferredInlineMemberFuncDefs.clear(); +} + +/// HandleTagDeclDefinition - This callback is invoked each time a TagDecl to +/// (e.g. struct, union, enum, class) is completed. This allows the client hack +/// on the type, which can occur at any point in the file (because these can be +/// defined in declspecs). +void CIRGenerator::HandleTagDeclDefinition(TagDecl *D) { + if (Diags.hasErrorOccurred()) + return; + + // Don't allow re-entrant calls to CIRGen triggered by PCH deserialization to + // emit deferred decls. + HandlingTopLevelDeclRAII HandlingDecl(*this, /*EmitDeferred=*/false); + + CGM->UpdateCompletedType(D); + + // For MSVC compatibility, treat declarations of static data members with + // inline initializers as definitions. + if (astCtx->getTargetInfo().getCXXABI().isMicrosoft()) { + llvm_unreachable("NYI"); + } + // For OpenMP emit declare reduction functions, if required. + if (astCtx->getLangOpts().OpenMP) { + llvm_unreachable("NYI"); + } +} + +void CIRGenerator::HandleTagDeclRequiredDefinition(const TagDecl *D) { + if (Diags.hasErrorOccurred()) + return; + + // Don't allow re-entrant calls to CIRGen triggered by PCH deserialization to + // emit deferred decls. + HandlingTopLevelDeclRAII HandlingDecl(*this, /*EmitDeferred=*/false); + + if (CGM->getModuleDebugInfo()) + llvm_unreachable("NYI"); +} + +void CIRGenerator::HandleCXXStaticMemberVarInstantiation(VarDecl *D) { + if (Diags.hasErrorOccurred()) + return; + + CGM->HandleCXXStaticMemberVarInstantiation(D); +} + +void CIRGenerator::CompleteTentativeDefinition(VarDecl *D) { + if (Diags.hasErrorOccurred()) + return; + + CGM->buildTentativeDefinition(D); +} diff --git a/clang/lib/CIR/CodeGen/CIRPasses.cpp b/clang/lib/CIR/CodeGen/CIRPasses.cpp new file mode 100644 index 000000000000..db72cc40ff82 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRPasses.cpp @@ -0,0 +1,50 @@ +//====- CIRPasses.cpp - Lowering from CIR to LLVM -------------------------===// +// +// 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 machinery for any CIR <-> CIR passes used by clang. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ASTContext.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/Passes.h" + +#include "mlir/Pass/Pass.h" +#include "mlir/Pass/PassManager.h" + +namespace cir { +mlir::LogicalResult runCIRToCIRPasses(mlir::ModuleOp theModule, + mlir::MLIRContext *mlirCtx, + clang::ASTContext &astCtx, + bool enableVerifier, bool enableLifetime, + llvm::StringRef lifetimeOpts, + bool &passOptParsingFailure) { + mlir::PassManager pm(mlirCtx); + passOptParsingFailure = false; + + pm.addPass(mlir::createMergeCleanupsPass()); + + if (enableLifetime) { + auto lifetimePass = mlir::createLifetimeCheckPass(&astCtx); + if (lifetimePass->initializeOptions(lifetimeOpts).failed()) { + passOptParsingFailure = true; + return mlir::failure(); + } + pm.addPass(std::move(lifetimePass)); + } + + pm.addPass(mlir::createLoweringPreparePass(&astCtx)); + + // FIXME: once CIRCodenAction fixes emission other than CIR we + // need to run this right before dialect emission. + pm.addPass(mlir::createDropASTPass()); + pm.enableVerifier(enableVerifier); + (void)mlir::applyPassManagerCLOptions(pm); + return pm.run(theModule); +} +} // namespace cir diff --git a/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp b/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp new file mode 100644 index 000000000000..fb17c7dbc3ff --- /dev/null +++ b/clang/lib/CIR/CodeGen/CIRRecordLayoutBuilder.cpp @@ -0,0 +1,684 @@ + +#include "CIRDataLayout.h" +#include "CIRGenBuilder.h" +#include "CIRGenModule.h" +#include "CIRGenTypes.h" + +#include "mlir/IR/BuiltinTypes.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecordLayout.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/ErrorHandling.h" + +#include + +using namespace llvm; +using namespace clang; +using namespace cir; + +namespace { +/// The CIRRecordLowering is responsible for lowering an ASTRecordLayout to a +/// mlir::Type. Some of the lowering is straightforward, some is not. TODO: Here +/// we detail some of the complexities and weirdnesses? +struct CIRRecordLowering final { + + // MemberInfo is a helper structure that contains information about a record + // member. In addition to the standard member types, there exists a sentinel + // member type that ensures correct rounding. + struct MemberInfo final { + CharUnits offset; + enum class InfoKind { VFPtr, VBPtr, Field, Base, VBase, Scissor } kind; + mlir::Type data; + union { + const FieldDecl *fieldDecl; + const CXXRecordDecl *cxxRecordDecl; + }; + MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data, + const FieldDecl *fieldDecl = nullptr) + : offset{offset}, kind{kind}, data{data}, fieldDecl{fieldDecl} {}; + MemberInfo(CharUnits offset, InfoKind kind, mlir::Type data, + const CXXRecordDecl *RD) + : offset{offset}, kind{kind}, data{data}, cxxRecordDecl{RD} {} + // MemberInfos are sorted so we define a < operator. + bool operator<(const MemberInfo &other) const { + return offset < other.offset; + } + }; + // The constructor. + CIRRecordLowering(CIRGenTypes &cirGenTypes, const RecordDecl *recordDecl, + bool isPacked); + + /// ---------------------- + /// Short helper routines. + + /// Constructs a MemberInfo instance from an offset and mlir::Type. + MemberInfo StorageInfo(CharUnits Offset, mlir::Type Data) { + return MemberInfo(Offset, MemberInfo::InfoKind::Field, Data); + } + + // Layout routines. + void setBitFieldInfo(const FieldDecl *FD, CharUnits StartOffset, + mlir::Type StorageType); + + void lower(bool nonVirtualBaseType); + void lowerUnion(); + + void computeVolatileBitfields(); + void accumulateBases(); + void accumulateVPtrs(); + void accumulateVBases(); + void accumulateFields(); + void accumulateBitFields(RecordDecl::field_iterator Field, + RecordDecl::field_iterator FieldEnd); + + mlir::Type getVFPtrType(); + + // Helper function to check if we are targeting AAPCS. + bool isAAPCS() const { + return astContext.getTargetInfo().getABI().startswith("aapcs"); + } + + /// Helper function to check if the target machine is BigEndian. + bool isBE() const { return astContext.getTargetInfo().isBigEndian(); } + + /// The Microsoft bitfield layout rule allocates discrete storage + /// units of the field's formal type and only combines adjacent + /// fields of the same formal type. We want to emit a layout with + /// these discrete storage units instead of combining them into a + /// continuous run. + bool isDiscreteBitFieldABI() { + return astContext.getTargetInfo().getCXXABI().isMicrosoft() || + recordDecl->isMsStruct(astContext); + } + + // The Itanium base layout rule allows virtual bases to overlap + // other bases, which complicates layout in specific ways. + // + // Note specifically that the ms_struct attribute doesn't change this. + bool isOverlappingVBaseABI() { + return !astContext.getTargetInfo().getCXXABI().isMicrosoft(); + } + // Recursively searches all of the bases to find out if a vbase is + // not the primary vbase of some base class. + bool hasOwnStorage(const CXXRecordDecl *Decl, const CXXRecordDecl *Query); + + CharUnits bitsToCharUnits(uint64_t bitOffset) { + return astContext.toCharUnitsFromBits(bitOffset); + } + + void calculateZeroInit(); + + CharUnits getSize(mlir::Type Ty) { + return CharUnits::fromQuantity(dataLayout.layout.getTypeSize(Ty)); + } + CharUnits getSizeInBits(mlir::Type Ty) { + return CharUnits::fromQuantity(dataLayout.layout.getTypeSizeInBits(Ty)); + } + CharUnits getAlignment(mlir::Type Ty) { + return CharUnits::fromQuantity(dataLayout.layout.getTypeABIAlignment(Ty)); + } + bool isZeroInitializable(const FieldDecl *FD) { + return cirGenTypes.isZeroInitializable(FD->getType()); + } + bool isZeroInitializable(const RecordDecl *RD) { + return cirGenTypes.isZeroInitializable(RD); + } + + mlir::Type getCharType() { + return mlir::cir::IntType::get(&cirGenTypes.getMLIRContext(), + astContext.getCharWidth(), + /*isSigned=*/false); + } + + /// Wraps mlir::cir::IntType with some implicit arguments. + mlir::Type getUIntNType(uint64_t NumBits) { + unsigned AlignedBits = llvm::PowerOf2Ceil(NumBits); + AlignedBits = std::max(8u, AlignedBits); + return mlir::cir::IntType::get(&cirGenTypes.getMLIRContext(), AlignedBits, + /*isSigned=*/false); + } + + mlir::Type getByteArrayType(CharUnits numberOfChars) { + assert(!numberOfChars.isZero() && "Empty byte arrays aren't allowed."); + mlir::Type type = getCharType(); + return numberOfChars == CharUnits::One() + ? type + : mlir::cir::ArrayType::get(type.getContext(), type, + numberOfChars.getQuantity()); + } + + // Gets the llvm Basesubobject type from a CXXRecordDecl. + mlir::Type getStorageType(const CXXRecordDecl *RD) { + return cirGenTypes.getCIRGenRecordLayout(RD).getBaseSubobjectCIRType(); + } + + mlir::Type getStorageType(const FieldDecl *fieldDecl) { + auto type = cirGenTypes.convertTypeForMem(fieldDecl->getType()); + assert(!fieldDecl->isBitField() && "bit fields NYI"); + if (!fieldDecl->isBitField()) + return type; + + // if (isDiscreteBitFieldABI()) + // return type; + + // return getUIntNType(std::min(fielddecl->getBitWidthValue(astContext), + // static_cast(astContext.toBits(getSize(type))))); + llvm_unreachable("getStorageType only supports nonBitFields at this point"); + } + + uint64_t getFieldBitOffset(const FieldDecl *fieldDecl) { + return astRecordLayout.getFieldOffset(fieldDecl->getFieldIndex()); + } + + /// Fills out the structures that are ultimately consumed. + void fillOutputFields(); + + void appendPaddingBytes(CharUnits Size) { + if (!Size.isZero()) + fieldTypes.push_back(getByteArrayType(Size)); + } + + CIRGenTypes &cirGenTypes; + CIRGenBuilderTy &builder; + const ASTContext &astContext; + const RecordDecl *recordDecl; + const CXXRecordDecl *cxxRecordDecl; + const ASTRecordLayout &astRecordLayout; + // Helpful intermediate data-structures + std::vector members; + // Output fields, consumed by CIRGenTypes::computeRecordLayout + llvm::SmallVector fieldTypes; + llvm::DenseMap fields; + llvm::DenseMap bitFields; + llvm::DenseMap nonVirtualBases; + llvm::DenseMap virtualBases; + CIRDataLayout dataLayout; + bool IsZeroInitializable : 1; + bool IsZeroInitializableAsBase : 1; + bool isPacked : 1; + +private: + CIRRecordLowering(const CIRRecordLowering &) = delete; + void operator=(const CIRRecordLowering &) = delete; +}; +} // namespace + +CIRRecordLowering::CIRRecordLowering(CIRGenTypes &cirGenTypes, + const RecordDecl *recordDecl, + bool isPacked) + : cirGenTypes{cirGenTypes}, builder{cirGenTypes.getBuilder()}, + astContext{cirGenTypes.getContext()}, recordDecl{recordDecl}, + cxxRecordDecl{llvm::dyn_cast(recordDecl)}, + astRecordLayout{cirGenTypes.getContext().getASTRecordLayout(recordDecl)}, + dataLayout{cirGenTypes.getModule().getModule()}, + IsZeroInitializable(true), + IsZeroInitializableAsBase(true), isPacked{isPacked} {} + +void CIRRecordLowering::setBitFieldInfo(const FieldDecl *FD, + CharUnits StartOffset, + mlir::Type StorageType) { + CIRGenBitFieldInfo &Info = bitFields[FD->getCanonicalDecl()]; + Info.IsSigned = FD->getType()->isSignedIntegerOrEnumerationType(); + Info.Offset = + (unsigned)(getFieldBitOffset(FD) - astContext.toBits(StartOffset)); + Info.Size = FD->getBitWidthValue(astContext); + Info.StorageSize = getSizeInBits(StorageType).getQuantity(); + Info.StorageOffset = StartOffset; + + if (Info.Size > Info.StorageSize) + Info.Size = Info.StorageSize; + // Reverse the bit offsets for big endian machines. Because we represent + // a bitfield as a single large integer load, we can imagine the bits + // counting from the most-significant-bit instead of the + // least-significant-bit. + if (dataLayout.isBigEndian()) + Info.Offset = Info.StorageSize - (Info.Offset + Info.Size); + + Info.VolatileStorageSize = 0; + Info.VolatileOffset = 0; + Info.VolatileStorageOffset = CharUnits::Zero(); +} + +void CIRRecordLowering::lower(bool nonVirtualBaseType) { + if (recordDecl->isUnion()) { + lowerUnion(); + computeVolatileBitfields(); + return; + } + + CharUnits Size = nonVirtualBaseType ? astRecordLayout.getNonVirtualSize() + : astRecordLayout.getSize(); + if (recordDecl->isUnion()) { + llvm_unreachable("NYI"); + // lowerUnion(); + // computeVolatileBitfields(); + return; + } + accumulateFields(); + + // RD implies C++ + if (cxxRecordDecl) { + accumulateVPtrs(); + accumulateBases(); + if (members.empty()) { + appendPaddingBytes(Size); + computeVolatileBitfields(); + return; + } + if (!nonVirtualBaseType) + accumulateVBases(); + } + + llvm::stable_sort(members); + // TODO: implement clipTailPadding once bitfields are implemented + // TODO: implemented packed structs + // TODO: implement padding + // TODO: support zeroInit + fillOutputFields(); + computeVolatileBitfields(); +} + +void CIRRecordLowering::lowerUnion() { + CharUnits LayoutSize = astRecordLayout.getSize(); + mlir::Type StorageType = nullptr; + bool SeenNamedMember = false; + // Iterate through the fields setting bitFieldInfo and the Fields array. Also + // locate the "most appropriate" storage type. The heuristic for finding the + // storage type isn't necessary, the first (non-0-length-bitfield) field's + // type would work fine and be simpler but would be different than what we've + // been doing and cause lit tests to change. + for (const auto *Field : recordDecl->fields()) { + if (Field->isBitField()) { + if (Field->isZeroLengthBitField(astContext)) + continue; + llvm_unreachable("NYI"); + } + fields[Field->getCanonicalDecl()] = 0; + auto FieldType = getStorageType(Field); + // Compute zero-initializable status. + // This union might not be zero initialized: it may contain a pointer to + // data member which might have some exotic initialization sequence. + // If this is the case, then we aught not to try and come up with a "better" + // type, it might not be very easy to come up with a Constant which + // correctly initializes it. + if (!SeenNamedMember) { + SeenNamedMember = Field->getIdentifier(); + if (!SeenNamedMember) + if (const auto *FieldRD = Field->getType()->getAsRecordDecl()) + SeenNamedMember = FieldRD->findFirstNamedDataMember(); + if (SeenNamedMember && !isZeroInitializable(Field)) { + IsZeroInitializable = IsZeroInitializableAsBase = false; + StorageType = FieldType; + } + } + // Because our union isn't zero initializable, we won't be getting a better + // storage type. + if (!IsZeroInitializable) + continue; + + // Conditionally update our storage type if we've got a new "better" one. + if (!StorageType || getAlignment(FieldType) > getAlignment(StorageType) || + (getAlignment(FieldType) == getAlignment(StorageType) && + getSize(FieldType) > getSize(StorageType))) + StorageType = FieldType; + + // NOTE(cir): Track all union member's types, not just the largest one. It + // allows for proper type-checking and retain more info for analisys. + fieldTypes.push_back(FieldType); + } + // If we have no storage type just pad to the appropriate size and return. + if (!StorageType) + llvm_unreachable("no-storage union NYI"); + // If our storage size was bigger than our required size (can happen in the + // case of packed bitfields on Itanium) then just use an I8 array. + if (LayoutSize < getSize(StorageType)) + StorageType = getByteArrayType(LayoutSize); + // NOTE(cir): Defer padding calculations to the lowering process. + // appendPaddingBytes(LayoutSize - getSize(StorageType)); + // Set packed if we need it. + if (LayoutSize % getAlignment(StorageType)) + isPacked = true; +} + +bool CIRRecordLowering::hasOwnStorage(const CXXRecordDecl *Decl, + const CXXRecordDecl *Query) { + const ASTRecordLayout &DeclLayout = astContext.getASTRecordLayout(Decl); + if (DeclLayout.isPrimaryBaseVirtual() && DeclLayout.getPrimaryBase() == Query) + return false; + for (const auto &Base : Decl->bases()) + if (!hasOwnStorage(Base.getType()->getAsCXXRecordDecl(), Query)) + return false; + return true; +} + +/// The AAPCS that defines that, when possible, bit-fields should +/// be accessed using containers of the declared type width: +/// When a volatile bit-field is read, and its container does not overlap with +/// any non-bit-field member or any zero length bit-field member, its container +/// must be read exactly once using the access width appropriate to the type of +/// the container. When a volatile bit-field is written, and its container does +/// not overlap with any non-bit-field member or any zero-length bit-field +/// member, its container must be read exactly once and written exactly once +/// using the access width appropriate to the type of the container. The two +/// accesses are not atomic. +/// +/// Enforcing the width restriction can be disabled using +/// -fno-aapcs-bitfield-width. +void CIRRecordLowering::computeVolatileBitfields() { + if (!isAAPCS() || + !cirGenTypes.getModule().getCodeGenOpts().AAPCSBitfieldWidth) + return; + + for ([[maybe_unused]] auto &I : bitFields) { + assert(!UnimplementedFeature::armComputeVolatileBitfields()); + } +} + +void CIRRecordLowering::accumulateBases() { + // If we've got a primary virtual base, we need to add it with the bases. + if (astRecordLayout.isPrimaryBaseVirtual()) { + llvm_unreachable("NYI"); + } + + // Accumulate the non-virtual bases. + for ([[maybe_unused]] const auto &Base : cxxRecordDecl->bases()) { + if (Base.isVirtual()) + continue; + // Bases can be zero-sized even if not technically empty if they + // contain only a trailing array member. + const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + if (!BaseDecl->isEmpty() && + !astContext.getASTRecordLayout(BaseDecl).getNonVirtualSize().isZero()) { + members.push_back(MemberInfo(astRecordLayout.getBaseClassOffset(BaseDecl), + MemberInfo::InfoKind::Base, + getStorageType(BaseDecl), BaseDecl)); + } + } +} + +void CIRRecordLowering::accumulateVBases() { + CharUnits ScissorOffset = astRecordLayout.getNonVirtualSize(); + // In the itanium ABI, it's possible to place a vbase at a dsize that is + // smaller than the nvsize. Here we check to see if such a base is placed + // before the nvsize and set the scissor offset to that, instead of the + // nvsize. + if (isOverlappingVBaseABI()) + for (const auto &Base : cxxRecordDecl->vbases()) { + const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + if (BaseDecl->isEmpty()) + continue; + // If the vbase is a primary virtual base of some base, then it doesn't + // get its own storage location but instead lives inside of that base. + if (astContext.isNearlyEmpty(BaseDecl) && + !hasOwnStorage(cxxRecordDecl, BaseDecl)) + continue; + ScissorOffset = std::min(ScissorOffset, + astRecordLayout.getVBaseClassOffset(BaseDecl)); + } + members.push_back(MemberInfo(ScissorOffset, MemberInfo::InfoKind::Scissor, + mlir::Type{}, cxxRecordDecl)); + for (const auto &Base : cxxRecordDecl->vbases()) { + const CXXRecordDecl *BaseDecl = Base.getType()->getAsCXXRecordDecl(); + if (BaseDecl->isEmpty()) + continue; + CharUnits Offset = astRecordLayout.getVBaseClassOffset(BaseDecl); + // If the vbase is a primary virtual base of some base, then it doesn't + // get its own storage location but instead lives inside of that base. + if (isOverlappingVBaseABI() && astContext.isNearlyEmpty(BaseDecl) && + !hasOwnStorage(cxxRecordDecl, BaseDecl)) { + members.push_back( + MemberInfo(Offset, MemberInfo::InfoKind::VBase, nullptr, BaseDecl)); + continue; + } + // If we've got a vtordisp, add it as a storage type. + if (astRecordLayout.getVBaseOffsetsMap() + .find(BaseDecl) + ->second.hasVtorDisp()) + members.push_back( + StorageInfo(Offset - CharUnits::fromQuantity(4), getUIntNType(32))); + members.push_back(MemberInfo(Offset, MemberInfo::InfoKind::VBase, + getStorageType(BaseDecl), BaseDecl)); + } +} + +void CIRRecordLowering::accumulateVPtrs() { + if (astRecordLayout.hasOwnVFPtr()) + members.push_back(MemberInfo(CharUnits::Zero(), MemberInfo::InfoKind::VFPtr, + getVFPtrType())); + if (astRecordLayout.hasOwnVBPtr()) + llvm_unreachable("NYI"); +} + +mlir::Type CIRRecordLowering::getVFPtrType() { + // FIXME: replay LLVM codegen for now, perhaps add a vtable ptr special + // type so it's a bit more clear and C++ idiomatic. + return builder.getVirtualFnPtrType(); +} + +void CIRRecordLowering::fillOutputFields() { + for (auto &member : members) { + if (member.data) + fieldTypes.push_back(member.data); + if (member.kind == MemberInfo::InfoKind::Field) { + if (member.fieldDecl) + fields[member.fieldDecl->getCanonicalDecl()] = fieldTypes.size() - 1; + // A field without storage must be a bitfield. + if (!member.data) + setBitFieldInfo(member.fieldDecl, member.offset, fieldTypes.back()); + } else if (member.kind == MemberInfo::InfoKind::Base) { + nonVirtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1; + } else if (member.kind == MemberInfo::InfoKind::VBase) { + virtualBases[member.cxxRecordDecl] = fieldTypes.size() - 1; + } + } +} + +void CIRRecordLowering::accumulateBitFields( + RecordDecl::field_iterator Field, RecordDecl::field_iterator FieldEnd) { + // Run stores the first element of the current run of bitfields. FieldEnd is + // used as a special value to note that we don't have a current run. A + // bitfield run is a contiguous collection of bitfields that can be stored in + // the same storage block. Zero-sized bitfields and bitfields that would + // cross an alignment boundary break a run and start a new one. + RecordDecl::field_iterator Run = FieldEnd; + // Tail is the offset of the first bit off the end of the current run. It's + // used to determine if the ASTRecordLayout is treating these two bitfields as + // contiguous. StartBitOffset is offset of the beginning of the Run. + uint64_t StartBitOffset, Tail = 0; + if (isDiscreteBitFieldABI()) { + llvm_unreachable("NYI"); + } + + // Check if OffsetInRecord (the size in bits of the current run) is better + // as a single field run. When OffsetInRecord has legal integer width, and + // its bitfield offset is naturally aligned, it is better to make the + // bitfield a separate storage component so as it can be accessed directly + // with lower cost. + auto IsBetterAsSingleFieldRun = [&](uint64_t OffsetInRecord, + uint64_t StartBitOffset) { + if (OffsetInRecord >= 64) // See IntType::verify + return true; + if (!cirGenTypes.getModule().getCodeGenOpts().FineGrainedBitfieldAccesses) + return false; + llvm_unreachable("NYI"); + // if (OffsetInRecord < 8 || !llvm::isPowerOf2_64(OffsetInRecord) || + // !DataLayout.fitsInLegalInteger(OffsetInRecord)) + // return false; + // Make sure StartBitOffset is naturally aligned if it is treated as an + // IType integer. + // if (StartBitOffset % + // astContext.toBits(getAlignment(getUIntNType(OffsetInRecord))) != + // 0) + // return false; + return true; + }; + + // The start field is better as a single field run. + bool StartFieldAsSingleRun = false; + for (;;) { + // Check to see if we need to start a new run. + if (Run == FieldEnd) { + // If we're out of fields, return. + if (Field == FieldEnd) + break; + // Any non-zero-length bitfield can start a new run. + if (!Field->isZeroLengthBitField(astContext)) { + Run = Field; + StartBitOffset = getFieldBitOffset(*Field); + Tail = StartBitOffset + Field->getBitWidthValue(astContext); + StartFieldAsSingleRun = + IsBetterAsSingleFieldRun(Tail - StartBitOffset, StartBitOffset); + } + ++Field; + continue; + } + + // If the start field of a new run is better as a single run, or if current + // field (or consecutive fields) is better as a single run, or if current + // field has zero width bitfield and either UseZeroLengthBitfieldAlignment + // or UseBitFieldTypeAlignment is set to true, or if the offset of current + // field is inconsistent with the offset of previous field plus its offset, + // skip the block below and go ahead to emit the storage. Otherwise, try to + // add bitfields to the run. + if (!StartFieldAsSingleRun && Field != FieldEnd && + !IsBetterAsSingleFieldRun(Tail - StartBitOffset, StartBitOffset) && + (!Field->isZeroLengthBitField(astContext) || + (!astContext.getTargetInfo().useZeroLengthBitfieldAlignment() && + !astContext.getTargetInfo().useBitFieldTypeAlignment())) && + Tail == getFieldBitOffset(*Field)) { + Tail += Field->getBitWidthValue(astContext); + ++Field; + continue; + } + + // We've hit a break-point in the run and need to emit a storage field. + auto Type = getUIntNType(Tail - StartBitOffset); + // Add the storage member to the record and set the bitfield info for all of + // the bitfields in the run. Bitfields get the offset of their storage but + // come afterward and remain there after a stable sort. + members.push_back(StorageInfo(bitsToCharUnits(StartBitOffset), Type)); + for (; Run != Field; ++Run) + members.push_back(MemberInfo(bitsToCharUnits(StartBitOffset), + MemberInfo::InfoKind::Field, nullptr, *Run)); + Run = FieldEnd; + StartFieldAsSingleRun = false; + } +} + +void CIRRecordLowering::accumulateFields() { + for (RecordDecl::field_iterator field = recordDecl->field_begin(), + fieldEnd = recordDecl->field_end(); + field != fieldEnd;) { + if (field->isBitField()) { + RecordDecl::field_iterator start = field; + // Iterate to gather the list of bitfields. + for (++field; field != fieldEnd && field->isBitField(); ++field) + ; + accumulateBitFields(start, field); + } else if (!field->isZeroSize(astContext)) { + members.push_back(MemberInfo{bitsToCharUnits(getFieldBitOffset(*field)), + MemberInfo::InfoKind::Field, + getStorageType(*field), *field}); + ++field; + } else { + // TODO(cir): do we want to do anything special about zero size + // members? + ++field; + } + } +} + +std::unique_ptr +CIRGenTypes::computeRecordLayout(const RecordDecl *D, + mlir::cir::StructType *Ty) { + CIRRecordLowering builder(*this, D, /*packed=*/false); + + builder.lower(/*nonVirtualBaseType=*/false); + + // If we're in C++, compute the base subobject type. + mlir::cir::StructType *BaseTy = nullptr; + if (llvm::isa(D) && !D->isUnion() && + !D->hasAttr()) { + BaseTy = Ty; + if (builder.astRecordLayout.getNonVirtualSize() != + builder.astRecordLayout.getSize()) { + CIRRecordLowering baseBuilder(*this, D, /*Packed=*/builder.isPacked); + auto baseIdentifier = getRecordTypeName(D, ".base"); + *BaseTy = Builder.getStructTy(baseBuilder.fieldTypes, baseIdentifier, + /*incomplete=*/false, /*packed=*/false, D); + // TODO(cir): add something like addRecordTypeName + + // BaseTy and Ty must agree on their packedness for getCIRFieldNo to work + // on both of them with the same index. + assert(builder.isPacked == baseBuilder.isPacked && + "Non-virtual and complete types must agree on packedness"); + } + } + + // Fill in the struct *after* computing the base type. Filling in the body + // signifies that the type is no longer opaque and record layout is complete, + // but we may need to recursively layout D while laying D out as a base type. + *Ty = Builder.getStructTy(builder.fieldTypes, getRecordTypeName(D, ""), + /*incomplete=*/false, /*packed=*/false, D); + + auto RL = std::make_unique( + Ty ? *Ty : mlir::cir::StructType{}, + BaseTy ? *BaseTy : mlir::cir::StructType{}, + (bool)builder.IsZeroInitializable, + (bool)builder.IsZeroInitializableAsBase); + + RL->NonVirtualBases.swap(builder.nonVirtualBases); + RL->CompleteObjectVirtualBases.swap(builder.virtualBases); + + // Add all the field numbers. + RL->FieldInfo.swap(builder.fields); + + // Add bitfield info. + RL->BitFields.swap(builder.bitFields); + + // Dump the layout, if requested. + if (getContext().getLangOpts().DumpRecordLayouts) { + llvm_unreachable("NYI"); + } + + // TODO: implement verification + return RL; +} + +CIRGenBitFieldInfo CIRGenBitFieldInfo::MakeInfo(CIRGenTypes &Types, + const FieldDecl *FD, + uint64_t Offset, uint64_t Size, + uint64_t StorageSize, + CharUnits StorageOffset) { + llvm_unreachable("NYI"); +} + +CIRDataLayout::CIRDataLayout(mlir::ModuleOp modOp) : layout{modOp} { + auto dlSpec = modOp->getAttr(mlir::DLTIDialect::kDataLayoutAttrName) + .dyn_cast(); + assert(dlSpec && "expected dl_spec in the module"); + auto entries = dlSpec.getEntries(); + + for (auto entry : entries) { + auto entryKey = entry.getKey(); + auto strKey = entryKey.dyn_cast(); + if (!strKey) + continue; + auto entryName = strKey.strref(); + if (entryName == mlir::DLTIDialect::kDataLayoutEndiannessKey) { + auto value = entry.getValue().dyn_cast(); + assert(value && "expected string attribute"); + auto endian = value.getValue(); + if (endian == mlir::DLTIDialect::kDataLayoutEndiannessBig) + bigEndian = true; + else if (endian == mlir::DLTIDialect::kDataLayoutEndiannessLittle) + bigEndian = false; + else + llvm_unreachable("unknown endianess"); + } + } +} diff --git a/clang/lib/CIR/CodeGen/CMakeLists.txt b/clang/lib/CIR/CodeGen/CMakeLists.txt new file mode 100644 index 000000000000..a379ed464316 --- /dev/null +++ b/clang/lib/CIR/CodeGen/CMakeLists.txt @@ -0,0 +1,70 @@ +set( + LLVM_LINK_COMPONENTS + Core + Support +) + +get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) + +add_clang_library(clangCIR + CIRGenBuiltin.cpp + CIRGenCXX.cpp + CIRGenCXXABI.cpp + CIRGenCall.cpp + CIRGenClass.cpp + CIRGenCleanup.cpp + CIRGenCoroutine.cpp + CIRGenDecl.cpp + CIRGenDeclCXX.cpp + CIRGenException.cpp + CIRGenExpr.cpp + CIRGenExprConst.cpp + CIRGenExprAgg.cpp + CIRGenExprCXX.cpp + CIRGenExprScalar.cpp + CIRGenFunction.cpp + CIRGenItaniumCXXABI.cpp + CIRGenModule.cpp + CIRGenStmt.cpp + CIRGenTBAA.cpp + CIRGenTypes.cpp + CIRGenVTables.cpp + CIRGenerator.cpp + CIRPasses.cpp + CIRRecordLayoutBuilder.cpp + ConstantInitBuilder.cpp + TargetInfo.cpp + + DEPENDS + MLIRCIR + MLIRCIROpsIncGen + MLIRCIRASTAttrInterfacesIncGen + ${dialect_libs} + + LINK_LIBS + clangAST + clangBasic + clangLex + ${dialect_libs} + MLIRCIR + MLIRCIRTransforms + MLIRCIRASTAttrInterfaces + MLIRAffineToStandard + MLIRAnalysis + MLIRDLTIDialect + MLIRFuncToLLVM + MLIRIR + MLIRLLVMCommonConversion + MLIRLLVMDialect + MLIRLLVMToLLVMIRTranslation + MLIRMemRefDialect + MLIRMemRefToLLVM + MLIRParser + MLIRPass + MLIRSCFToControlFlow + MLIRSideEffectInterfaces + MLIRSupport + MLIRTargetLLVMIRImport + MLIRTargetLLVMIRExport + MLIRTransforms +) diff --git a/clang/lib/CIR/CodeGen/CallingConv.h b/clang/lib/CIR/CodeGen/CallingConv.h new file mode 100644 index 000000000000..e6b41cdb550c --- /dev/null +++ b/clang/lib/CIR/CodeGen/CallingConv.h @@ -0,0 +1,43 @@ +//===- CallingConv.h - CIR Calling Conventions ------------*- C++ -------*-===// +// +// 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 defines CIR's set of calling conventions. +// +//===----------------------------------------------------------------------===// + +#ifndef CLANG_CIR_CALLINGCONV_H +#define CLANG_CIR_CALLINGCONV_H + +// TODO: This whole file needs translated to CIR + +namespace cir { + +/// CallingConv Namespace - This namespace contains an enum with a value for the +/// well-known calling conventions. +namespace CallingConv { + +/// LLVM IR allows to use arbitrary numbers as calling convention identifiers. +/// TODO: What should we do for this for CIR +using ID = unsigned; + +/// A set of enums which specify the assigned numeric values for known llvm +/// calling conventions. +/// LLVM Calling Convention Represetnation +enum { + /// C - The default llvm calling convention, compatible with C. This + /// convention is the only calling convention that supports varargs calls. As + /// with typical C calling conventions, the callee/caller have to tolerate + /// certain amounts of prototype mismatch. + C = 0, +}; + +} // namespace CallingConv + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/ConstantInitBuilder.cpp b/clang/lib/CIR/CodeGen/ConstantInitBuilder.cpp new file mode 100644 index 000000000000..89852f29e648 --- /dev/null +++ b/clang/lib/CIR/CodeGen/ConstantInitBuilder.cpp @@ -0,0 +1,327 @@ +//===--- ConstantInitBuilder.cpp - Global initializer builder -------------===// +// +// 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 defines out-of-line routines for building initializers for +// global variables, in particular the kind of globals that are implicitly +// introduced by various language ABIs. +// +//===----------------------------------------------------------------------===// + +#include "ConstantInitBuilder.h" +#include "CIRGenModule.h" + +using namespace clang; +using namespace cir; + +ConstantInitBuilderBase::ConstantInitBuilderBase(CIRGenModule &CGM) + : CGM(CGM), builder(CGM.getBuilder()) {} + +mlir::Type ConstantInitFuture::getType() const { + assert(Data && "dereferencing null future"); + if (Data.is()) { + auto attr = Data.get().dyn_cast(); + assert(attr && "expected typed attribute"); + return attr.getType(); + } else { + llvm_unreachable("Only sypport typed attributes here"); + } +} + +void ConstantInitFuture::abandon() { + assert(Data && "abandoning null future"); + if (auto builder = Data.dyn_cast()) { + builder->abandon(0); + } + Data = nullptr; +} + +void ConstantInitFuture::installInGlobal(mlir::cir::GlobalOp GV) { + assert(Data && "installing null future"); + if (Data.is()) { + CIRGenModule::setInitializer(GV, Data.get()); + } else { + llvm_unreachable("NYI"); + // auto &builder = *Data.get(); + // assert(builder.Buffer.size() == 1); + // builder.setGlobalInitializer(GV, builder.Buffer[0]); + // builder.Buffer.clear(); + // Data = nullptr; + } +} + +ConstantInitFuture +ConstantInitBuilderBase::createFuture(mlir::Attribute initializer) { + assert(Buffer.empty() && "buffer not current empty"); + Buffer.push_back(initializer); + return ConstantInitFuture(this); +} + +// Only used in this file. +inline ConstantInitFuture::ConstantInitFuture(ConstantInitBuilderBase *builder) + : Data(builder) { + assert(!builder->Frozen); + assert(builder->Buffer.size() == 1); + assert(builder->Buffer[0] != nullptr); +} + +mlir::cir::GlobalOp ConstantInitBuilderBase::createGlobal( + mlir::Attribute initializer, const llvm::Twine &name, CharUnits alignment, + bool constant, mlir::cir::GlobalLinkageKind linkage, + unsigned addressSpace) { + llvm_unreachable("NYI"); + // auto GV = + // new llvm::GlobalVariable(CGM.getModule(), initializer->getType(), + // constant, linkage, initializer, name, + // /*insert before*/ nullptr, + // llvm::GlobalValue::NotThreadLocal, + // addressSpace); + // GV->setAlignment(alignment.getAsAlign()); + // resolveSelfReferences(GV); + // return GV; +} + +void ConstantInitBuilderBase::setGlobalInitializer( + mlir::cir::GlobalOp GV, mlir::Attribute initializer) { + CIRGenModule::setInitializer(GV, initializer); + + if (!SelfReferences.empty()) + resolveSelfReferences(GV); +} + +void ConstantInitBuilderBase::resolveSelfReferences(mlir::cir::GlobalOp GV) { + llvm_unreachable("NYI"); + // for (auto &entry : SelfReferences) { + // mlir::Attribute resolvedReference = + // llvm::ConstantExpr::getInBoundsGetElementPtr(GV->getValueType(), GV, + // entry.Indices); + // auto dummy = entry.Dummy; + // dummy->replaceAllUsesWith(resolvedReference); + // dummy->eraseFromParent(); + // } + // SelfReferences.clear(); +} + +void ConstantInitBuilderBase::abandon(size_t newEnd) { + llvm_unreachable("NYI"); + // // Remove all the entries we've added. + // Buffer.erase(Buffer.begin() + newEnd, Buffer.end()); + + // // If we're abandoning all the way to the beginning, destroy + // // all the self-references, because we might not get another + // // opportunity. + // if (newEnd == 0) { + // for (auto &entry : SelfReferences) { + // auto dummy = entry.Dummy; + // dummy->replaceAllUsesWith(llvm::PoisonValue::get(dummy->getType())); + // dummy->eraseFromParent(); + // } + // SelfReferences.clear(); + // } +} + +void ConstantAggregateBuilderBase::addSize(CharUnits size) { + add(Builder.CGM.getSize(size)); +} + +mlir::Attribute +ConstantAggregateBuilderBase::getRelativeOffset(mlir::cir::IntType offsetType, + mlir::Attribute target) { + return getRelativeOffsetToPosition(offsetType, target, + Builder.Buffer.size() - Begin); +} + +mlir::Attribute ConstantAggregateBuilderBase::getRelativeOffsetToPosition( + mlir::cir::IntType offsetType, mlir::Attribute target, size_t position) { + llvm_unreachable("NYI"); + // // Compute the address of the relative-address slot. + // auto base = getAddrOfPosition(offsetType, position); + + // // Subtract. + // base = llvm::ConstantExpr::getPtrToInt(base, Builder.CGM.IntPtrTy); + // target = llvm::ConstantExpr::getPtrToInt(target, Builder.CGM.IntPtrTy); + // mlir::Attribute offset = llvm::ConstantExpr::getSub(target, base); + + // // Truncate to the relative-address type if necessary. + // if (Builder.CGM.IntPtrTy != offsetType) { + // offset = llvm::ConstantExpr::getTrunc(offset, offsetType); + // } + + // return offset; +} + +mlir::Attribute +ConstantAggregateBuilderBase::getAddrOfPosition(mlir::Type type, + size_t position) { + llvm_unreachable("NYI"); + // // Make a global variable. We will replace this with a GEP to this + // // position after installing the initializer. + // auto dummy = new llvm::GlobalVariable(Builder.CGM.getModule(), type, true, + // llvm::GlobalVariable::PrivateLinkage, + // nullptr, ""); + // Builder.SelfReferences.emplace_back(dummy); + // auto &entry = Builder.SelfReferences.back(); + // (void)getGEPIndicesTo(entry.Indices, position + Begin); + // return dummy; +} + +mlir::Attribute +ConstantAggregateBuilderBase::getAddrOfCurrentPosition(mlir::Type type) { + llvm_unreachable("NYI"); + // // Make a global variable. We will replace this with a GEP to this + // // position after installing the initializer. + // auto dummy = new llvm::GlobalVariable(Builder.CGM.getModule(), type, true, + // llvm::GlobalVariable::PrivateLinkage, + // nullptr, ""); + // Builder.SelfReferences.emplace_back(dummy); + // auto &entry = Builder.SelfReferences.back(); + // (void)getGEPIndicesToCurrentPosition(entry.Indices); + // return dummy; +} + +void ConstantAggregateBuilderBase::getGEPIndicesTo( + llvm::SmallVectorImpl &indices, size_t position) const { + llvm_unreachable("NYI"); + // // Recurse on the parent builder if present. + // if (Parent) { + // Parent->getGEPIndicesTo(indices, Begin); + + // // Otherwise, add an index to drill into the first level of pointer. + // } else { + // assert(indices.empty()); + // indices.push_back(llvm::ConstantInt::get(Builder.CGM.Int32Ty, 0)); + // } + + // assert(position >= Begin); + // // We have to use i32 here because struct GEPs demand i32 indices. + // // It's rather unlikely to matter in practice. + // indices.push_back( + // llvm::ConstantInt::get(Builder.CGM.Int32Ty, position - Begin)); +} + +ConstantAggregateBuilderBase::PlaceholderPosition +ConstantAggregateBuilderBase::addPlaceholderWithSize(mlir::Type type) { + llvm_unreachable("NYI"); + // // Bring the offset up to the last field. + // CharUnits offset = getNextOffsetFromGlobal(); + + // // Create the placeholder. + // auto position = addPlaceholder(); + + // // Advance the offset past that field. + // auto &layout = Builder.CGM.getDataLayout(); + // if (!Packed) + // offset = + // offset.alignTo(CharUnits::fromQuantity(layout.getABITypeAlign(type))); + // offset += CharUnits::fromQuantity(layout.getTypeStoreSize(type)); + + // CachedOffsetEnd = Builder.Buffer.size(); + // CachedOffsetFromGlobal = offset; + + // return position; +} + +CharUnits +ConstantAggregateBuilderBase::getOffsetFromGlobalTo(size_t end) const { + size_t cacheEnd = CachedOffsetEnd; + assert(cacheEnd <= end); + + // Fast path: if the cache is valid, just use it. + if (cacheEnd == end) { + return CachedOffsetFromGlobal; + } + + // If the cached range ends before the index at which the current + // aggregate starts, recurse for the parent. + CharUnits offset; + if (cacheEnd < Begin) { + assert(cacheEnd == 0); + assert(Parent && "Begin != 0 for root builder"); + cacheEnd = Begin; + offset = Parent->getOffsetFromGlobalTo(Begin); + } else { + offset = CachedOffsetFromGlobal; + } + + // Perform simple layout on the elements in cacheEnd..getType(); + // if (!Packed) + // offset = offset.alignTo( + // CharUnits::fromQuantity(layout.getABITypeAlign(elementType))); + // offset += + // CharUnits::fromQuantity(layout.getTypeStoreSize(elementType)); + // } while (++cacheEnd != end); + } + + // Cache and return. + CachedOffsetEnd = cacheEnd; + CachedOffsetFromGlobal = offset; + return offset; +} + +// FIXME(cir): ideally we should use CIRGenBuilder for both static function +// bellow by threading ConstantAggregateBuilderBase through +// ConstantAggregateBuilderBase. +static mlir::cir::ConstArrayAttr getConstArray(mlir::Attribute attrs, + mlir::cir::ArrayType arrayTy) { + return mlir::cir::ConstArrayAttr::get(arrayTy, attrs); +} + +mlir::Attribute ConstantAggregateBuilderBase::finishArray(mlir::Type eltTy) { + markFinished(); + + auto &buffer = getBuffer(); + assert((Begin < buffer.size() || (Begin == buffer.size() && eltTy)) && + "didn't add any array elements without element type"); + auto elts = llvm::ArrayRef(buffer).slice(Begin); + if (!eltTy) { + llvm_unreachable("NYI"); + // Uncomment this once we get a testcase. + // auto tAttr = elts[0].dyn_cast(); + // assert(tAttr && "expected typed attribute"); + // eltTy = tAttr.getType(); + } + + auto constant = getConstArray( + mlir::ArrayAttr::get(eltTy.getContext(), elts), + mlir::cir::ArrayType::get(eltTy.getContext(), eltTy, elts.size())); + buffer.erase(buffer.begin() + Begin, buffer.end()); + return constant; +} + +mlir::Attribute +ConstantAggregateBuilderBase::finishStruct(mlir::MLIRContext *ctx, + mlir::cir::StructType ty) { + markFinished(); + + auto &buffer = getBuffer(); + auto elts = llvm::ArrayRef(buffer).slice(Begin); + + if (ty == nullptr && elts.empty()) { + llvm_unreachable("NYI"); + } + + mlir::Attribute constant; + if (ty) { + llvm_unreachable("NYI"); + // assert(ty->isPacked() == Packed); + // constant = llvm::ConstantStruct::get(ty, elts); + } else { + const auto members = mlir::ArrayAttr::get(ctx, elts); + constant = Builder.CGM.getBuilder().getAnonConstStruct(members, Packed); + } + + buffer.erase(buffer.begin() + Begin, buffer.end()); + return constant; +} diff --git a/clang/lib/CIR/CodeGen/ConstantInitBuilder.h b/clang/lib/CIR/CodeGen/ConstantInitBuilder.h new file mode 100644 index 000000000000..7a32aa591182 --- /dev/null +++ b/clang/lib/CIR/CodeGen/ConstantInitBuilder.h @@ -0,0 +1,589 @@ +//===- ConstantInitBuilder.h - Builder for CIR attributes -------*- C++ -*-===// +// +// 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 convenient interface for building complex +// global initializers of the sort that are frequently required for +// language ABIs. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CIR_CODEGEN_CONSTANTINITBUILDER_H +#define LLVM_CLANG_CIR_CODEGEN_CONSTANTINITBUILDER_H + +#include "clang/AST/CharUnits.h" +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/SmallVector.h" + +#include "CIRGenBuilder.h" +#include "ConstantInitFuture.h" + +#include +#include + +using namespace clang; + +namespace cir { + +class CIRGenModule; + +/// A convenience builder class for complex constant initializers, +/// especially for anonymous global structures used by various language +/// runtimes. +/// +/// The basic usage pattern is expected to be something like: +/// ConstantInitBuilder builder(CGM); +/// auto toplevel = builder.beginStruct(); +/// toplevel.addInt(CGM.SizeTy, widgets.size()); +/// auto widgetArray = builder.beginArray(); +/// for (auto &widget : widgets) { +/// auto widgetDesc = widgetArray.beginStruct(); +/// widgetDesc.addInt(CGM.SizeTy, widget.getPower()); +/// widgetDesc.add(CGM.GetAddrOfConstantString(widget.getName())); +/// widgetDesc.add(CGM.GetAddrOfGlobal(widget.getInitializerDecl())); +/// widgetDesc.finishAndAddTo(widgetArray); +/// } +/// widgetArray.finishAndAddTo(toplevel); +/// auto global = toplevel.finishAndCreateGlobal("WIDGET_LIST", Align, +/// /*constant*/ true); +class ConstantInitBuilderBase { + struct SelfReference { + mlir::cir::GlobalOp Dummy; + llvm::SmallVector Indices; + + SelfReference(mlir::cir::GlobalOp dummy) : Dummy(dummy) {} + }; + CIRGenModule &CGM; + CIRGenBuilderTy &builder; + llvm::SmallVector Buffer; + std::vector SelfReferences; + bool Frozen = false; + + friend class ConstantInitFuture; + friend class ConstantAggregateBuilderBase; + template friend class ConstantAggregateBuilderTemplateBase; + +protected: + explicit ConstantInitBuilderBase(CIRGenModule &CGM); + + ~ConstantInitBuilderBase() { + assert(Buffer.empty() && "didn't claim all values out of buffer"); + assert(SelfReferences.empty() && "didn't apply all self-references"); + } + +private: + mlir::cir::GlobalOp + createGlobal(mlir::Attribute initializer, const llvm::Twine &name, + CharUnits alignment, bool constant = false, + mlir::cir::GlobalLinkageKind linkage = + mlir::cir::GlobalLinkageKind::InternalLinkage, + unsigned addressSpace = 0); + + ConstantInitFuture createFuture(mlir::Attribute initializer); + + void setGlobalInitializer(mlir::cir::GlobalOp GV, + mlir::Attribute initializer); + + void resolveSelfReferences(mlir::cir::GlobalOp GV); + + void abandon(size_t newEnd); +}; + +/// A concrete base class for struct and array aggregate +/// initializer builders. +class ConstantAggregateBuilderBase { +protected: + ConstantInitBuilderBase &Builder; + ConstantAggregateBuilderBase *Parent; + size_t Begin; + mutable size_t CachedOffsetEnd = 0; + bool Finished = false; + bool Frozen = false; + bool Packed = false; + mutable CharUnits CachedOffsetFromGlobal; + + llvm::SmallVectorImpl &getBuffer() { return Builder.Buffer; } + + const llvm::SmallVectorImpl &getBuffer() const { + return Builder.Buffer; + } + + ConstantAggregateBuilderBase(ConstantInitBuilderBase &builder, + ConstantAggregateBuilderBase *parent) + : Builder(builder), Parent(parent), Begin(builder.Buffer.size()) { + if (parent) { + assert(!parent->Frozen && "parent already has child builder active"); + parent->Frozen = true; + } else { + assert(!builder.Frozen && "builder already has child builder active"); + builder.Frozen = true; + } + } + + ~ConstantAggregateBuilderBase() { + assert(Finished && "didn't finish aggregate builder"); + } + + void markFinished() { + assert(!Frozen && "child builder still active"); + assert(!Finished && "builder already finished"); + Finished = true; + if (Parent) { + assert(Parent->Frozen && "parent not frozen while child builder active"); + Parent->Frozen = false; + } else { + assert(Builder.Frozen && "builder not frozen while child builder active"); + Builder.Frozen = false; + } + } + +public: + // Not copyable. + ConstantAggregateBuilderBase(const ConstantAggregateBuilderBase &) = delete; + ConstantAggregateBuilderBase & + operator=(const ConstantAggregateBuilderBase &) = delete; + + // Movable, mostly to allow returning. But we have to write this out + // properly to satisfy the assert in the destructor. + ConstantAggregateBuilderBase(ConstantAggregateBuilderBase &&other) + : Builder(other.Builder), Parent(other.Parent), Begin(other.Begin), + CachedOffsetEnd(other.CachedOffsetEnd), Finished(other.Finished), + Frozen(other.Frozen), Packed(other.Packed), + CachedOffsetFromGlobal(other.CachedOffsetFromGlobal) { + other.Finished = true; + } + ConstantAggregateBuilderBase & + operator=(ConstantAggregateBuilderBase &&other) = delete; + + /// Return the number of elements that have been added to + /// this struct or array. + size_t size() const { + assert(!this->Finished && "cannot query after finishing builder"); + assert(!this->Frozen && "cannot query while sub-builder is active"); + assert(this->Begin <= this->getBuffer().size()); + return this->getBuffer().size() - this->Begin; + } + + /// Return true if no elements have yet been added to this struct or array. + bool empty() const { return size() == 0; } + + /// Abandon this builder completely. + void abandon() { + markFinished(); + Builder.abandon(Begin); + } + + /// Add a new value to this initializer. + void add(mlir::Attribute value) { + assert(value && "adding null value to constant initializer"); + assert(!Finished && "cannot add more values after finishing builder"); + assert(!Frozen && "cannot add values while subbuilder is active"); + Builder.Buffer.push_back(value); + } + + /// Add an integer value of type size_t. + void addSize(CharUnits size); + + /// Add an integer value of a specific type. + void addInt(mlir::cir::IntType intTy, uint64_t value, bool isSigned = false) { + add(mlir::IntegerAttr::get(intTy, + llvm::APInt{intTy.getWidth(), value, isSigned})); + } + + /// Add a pointer of a specific type. + void addPointer(mlir::cir::PointerType ptrTy, uint64_t value) { + add(mlir::cir::ConstPtrAttr::get(ptrTy.getContext(), ptrTy, value)); + } + + /// Add a bitcast of a value to a specific type. + void addBitCast(mlir::Attribute value, mlir::Type type) { + llvm_unreachable("NYI"); + // add(llvm::ConstantExpr::getBitCast(value, type)); + } + + /// Add a bunch of new values to this initializer. + void addAll(llvm::ArrayRef values) { + assert(!Finished && "cannot add more values after finishing builder"); + assert(!Frozen && "cannot add values while subbuilder is active"); + Builder.Buffer.append(values.begin(), values.end()); + } + + /// Add a relative offset to the given target address, i.e. the + /// static difference between the target address and the address + /// of the relative offset. The target must be known to be defined + /// in the current linkage unit. The offset will have the given + /// integer type, which must be no wider than intptr_t. Some + /// targets may not fully support this operation. + void addRelativeOffset(mlir::cir::IntType type, mlir::Attribute target) { + llvm_unreachable("NYI"); + // add(getRelativeOffset(type, target)); + } + + /// Same as addRelativeOffset(), but instead relative to an element in this + /// aggregate, identified by its index. + void addRelativeOffsetToPosition(mlir::cir::IntType type, + mlir::Attribute target, size_t position) { + llvm_unreachable("NYI"); + // add(getRelativeOffsetToPosition(type, target, position)); + } + + /// Add a relative offset to the target address, plus a small + /// constant offset. This is primarily useful when the relative + /// offset is known to be a multiple of (say) four and therefore + /// the tag can be used to express an extra two bits of information. + void addTaggedRelativeOffset(mlir::cir::IntType type, mlir::Attribute address, + unsigned tag) { + llvm_unreachable("NYI"); + // mlir::Attribute offset = + // getRelativeOffset(type, address); if + // (tag) { + // offset = + // llvm::ConstantExpr::getAdd(offset, + // llvm::ConstantInt::get(type, tag)); + // } + // add(offset); + } + + /// Return the offset from the start of the initializer to the + /// next position, assuming no padding is required prior to it. + /// + /// This operation will not succeed if any unsized placeholders are + /// currently in place in the initializer. + CharUnits getNextOffsetFromGlobal() const { + assert(!Finished && "cannot add more values after finishing builder"); + assert(!Frozen && "cannot add values while subbuilder is active"); + return getOffsetFromGlobalTo(Builder.Buffer.size()); + } + + /// An opaque class to hold the abstract position of a placeholder. + class PlaceholderPosition { + size_t Index; + friend class ConstantAggregateBuilderBase; + PlaceholderPosition(size_t index) : Index(index) {} + }; + + /// Add a placeholder value to the structure. The returned position + /// can be used to set the value later; it will not be invalidated by + /// any intermediate operations except (1) filling the same position or + /// (2) finishing the entire builder. + /// + /// This is useful for emitting certain kinds of structure which + /// contain some sort of summary field, generally a count, before any + /// of the data. By emitting a placeholder first, the structure can + /// be emitted eagerly. + PlaceholderPosition addPlaceholder() { + assert(!Finished && "cannot add more values after finishing builder"); + assert(!Frozen && "cannot add values while subbuilder is active"); + Builder.Buffer.push_back(nullptr); + return Builder.Buffer.size() - 1; + } + + /// Add a placeholder, giving the expected type that will be filled in. + PlaceholderPosition addPlaceholderWithSize(mlir::Type expectedType); + + /// Fill a previously-added placeholder. + void fillPlaceholderWithInt(PlaceholderPosition position, + mlir::cir::IntType type, uint64_t value, + bool isSigned = false) { + llvm_unreachable("NYI"); + // fillPlaceholder(position, llvm::ConstantInt::get(type, value, isSigned)); + } + + /// Fill a previously-added placeholder. + void fillPlaceholder(PlaceholderPosition position, mlir::Attribute value) { + assert(!Finished && "cannot change values after finishing builder"); + assert(!Frozen && "cannot add values while subbuilder is active"); + mlir::Attribute &slot = Builder.Buffer[position.Index]; + assert(slot == nullptr && "placeholder already filled"); + slot = value; + } + + /// Produce an address which will eventually point to the next + /// position to be filled. This is computed with an indexed + /// getelementptr rather than by computing offsets. + /// + /// The returned pointer will have type T*, where T is the given type. This + /// type can differ from the type of the actual element. + mlir::Attribute getAddrOfCurrentPosition(mlir::Type type); + + /// Produce an address which points to a position in the aggregate being + /// constructed. This is computed with an indexed getelementptr rather than by + /// computing offsets. + /// + /// The returned pointer will have type T*, where T is the given type. This + /// type can differ from the type of the actual element. + mlir::Attribute getAddrOfPosition(mlir::Type type, size_t position); + + llvm::ArrayRef getGEPIndicesToCurrentPosition( + llvm::SmallVectorImpl &indices) { + getGEPIndicesTo(indices, Builder.Buffer.size()); + return indices; + } + +protected: + mlir::Attribute finishArray(mlir::Type eltTy); + mlir::Attribute finishStruct(mlir::MLIRContext *ctx, + mlir::cir::StructType structTy); + +private: + void getGEPIndicesTo(llvm::SmallVectorImpl &indices, + size_t position) const; + + mlir::Attribute getRelativeOffset(mlir::cir::IntType offsetType, + mlir::Attribute target); + + mlir::Attribute getRelativeOffsetToPosition(mlir::cir::IntType offsetType, + mlir::Attribute target, + size_t position); + + CharUnits getOffsetFromGlobalTo(size_t index) const; +}; + +template +class ConstantAggregateBuilderTemplateBase + : public Traits::AggregateBuilderBase { + using super = typename Traits::AggregateBuilderBase; + +public: + using InitBuilder = typename Traits::InitBuilder; + using ArrayBuilder = typename Traits::ArrayBuilder; + using StructBuilder = typename Traits::StructBuilder; + using AggregateBuilderBase = typename Traits::AggregateBuilderBase; + +protected: + ConstantAggregateBuilderTemplateBase(InitBuilder &builder, + AggregateBuilderBase *parent) + : super(builder, parent) {} + + Impl &asImpl() { return *static_cast(this); } + +public: + ArrayBuilder beginArray(mlir::Type eltTy = nullptr) { + return ArrayBuilder(static_cast(this->Builder), this, eltTy); + } + + StructBuilder beginStruct(mlir::cir::StructType ty = nullptr) { + return StructBuilder(static_cast(this->Builder), this, ty); + } + + /// Given that this builder was created by beginning an array or struct + /// component on the given parent builder, finish the array/struct + /// component and add it to the parent. + /// + /// It is an intentional choice that the parent is passed in explicitly + /// despite it being redundant with information already kept in the + /// builder. This aids in readability by making it easier to find the + /// places that add components to a builder, as well as "bookending" + /// the sub-builder more explicitly. + void finishAndAddTo(mlir::MLIRContext *ctx, AggregateBuilderBase &parent) { + assert(this->Parent == &parent && "adding to non-parent builder"); + parent.add(asImpl().finishImpl(ctx)); + } + + /// Given that this builder was created by beginning an array or struct + /// directly on a ConstantInitBuilder, finish the array/struct and + /// create a global variable with it as the initializer. + template + mlir::cir::GlobalOp finishAndCreateGlobal(mlir::MLIRContext *ctx, + As &&...args) { + assert(!this->Parent && "finishing non-root builder"); + return this->Builder.createGlobal(asImpl().finishImpl(ctx), + std::forward(args)...); + } + + /// Given that this builder was created by beginning an array or struct + /// directly on a ConstantInitBuilder, finish the array/struct and + /// set it as the initializer of the given global variable. + void finishAndSetAsInitializer(mlir::cir::GlobalOp global, + bool forVTable = false) { + assert(!this->Parent && "finishing non-root builder"); + mlir::Attribute init = asImpl().finishImpl(global.getContext()); + auto initCSA = init.dyn_cast(); + assert(initCSA && + "expected #cir.const_struct attribute to represent vtable data"); + return this->Builder.setGlobalInitializer( + global, forVTable ? mlir::cir::VTableAttr::get(initCSA.getType(), + initCSA.getMembers()) + : init); + } + + /// Given that this builder was created by beginning an array or struct + /// directly on a ConstantInitBuilder, finish the array/struct and + /// return a future which can be used to install the initializer in + /// a global later. + /// + /// This is useful for allowing a finished initializer to passed to + /// an API which will build the global. However, the "future" preserves + /// a dependency on the original builder; it is an error to pass it aside. + ConstantInitFuture finishAndCreateFuture(mlir::MLIRContext *ctx) { + assert(!this->Parent && "finishing non-root builder"); + return this->Builder.createFuture(asImpl().finishImpl(ctx)); + } +}; + +template +class ConstantArrayBuilderTemplateBase + : public ConstantAggregateBuilderTemplateBase { + using super = + ConstantAggregateBuilderTemplateBase; + +public: + using InitBuilder = typename Traits::InitBuilder; + using AggregateBuilderBase = typename Traits::AggregateBuilderBase; + +private: + mlir::Type EltTy; + + template friend class ConstantAggregateBuilderTemplateBase; + +protected: + ConstantArrayBuilderTemplateBase(InitBuilder &builder, + AggregateBuilderBase *parent, + mlir::Type eltTy) + : super(builder, parent), EltTy(eltTy) {} + +private: + /// Form an array constant from the values that have been added to this + /// builder. + mlir::Attribute finishImpl([[maybe_unused]] mlir::MLIRContext *ctx) { + return AggregateBuilderBase::finishArray(EltTy); + } +}; + +/// A template class designed to allow other frontends to +/// easily customize the builder classes used by ConstantInitBuilder, +/// and thus to extend the API to work with the abstractions they +/// prefer. This would probably not be necessary if C++ just +/// supported extension methods. +template +class ConstantStructBuilderTemplateBase + : public ConstantAggregateBuilderTemplateBase< + typename Traits::StructBuilder, Traits> { + using super = + ConstantAggregateBuilderTemplateBase; + +public: + using InitBuilder = typename Traits::InitBuilder; + using AggregateBuilderBase = typename Traits::AggregateBuilderBase; + +private: + mlir::cir::StructType StructTy; + + template friend class ConstantAggregateBuilderTemplateBase; + +protected: + ConstantStructBuilderTemplateBase(InitBuilder &builder, + AggregateBuilderBase *parent, + mlir::cir::StructType structTy) + : super(builder, parent), StructTy(structTy) { + if (structTy) { + llvm_unreachable("NYI"); + // this->Packed = structTy->isPacked(); + } + } + +public: + void setPacked(bool packed) { this->Packed = packed; } + + /// Use the given type for the struct if its element count is correct. + /// Don't add more elements after calling this. + void suggestType(mlir::cir::StructType structTy) { + if (this->size() == structTy.getNumElements()) { + StructTy = structTy; + } + } + +private: + /// Form an array constant from the values that have been added to this + /// builder. + mlir::Attribute finishImpl(mlir::MLIRContext *ctx) { + return AggregateBuilderBase::finishStruct(ctx, StructTy); + } +}; + +/// A template class designed to allow other frontends to +/// easily customize the builder classes used by ConstantInitBuilder, +/// and thus to extend the API to work with the abstractions they +/// prefer. This would probably not be necessary if C++ just +/// supported extension methods. +template +class ConstantInitBuilderTemplateBase : public ConstantInitBuilderBase { +protected: + ConstantInitBuilderTemplateBase(CIRGenModule &CGM) + : ConstantInitBuilderBase(CGM) {} + +public: + using InitBuilder = typename Traits::InitBuilder; + using ArrayBuilder = typename Traits::ArrayBuilder; + using StructBuilder = typename Traits::StructBuilder; + + ArrayBuilder beginArray(mlir::Type eltTy = nullptr) { + return ArrayBuilder(static_cast(*this), nullptr, eltTy); + } + + StructBuilder beginStruct(mlir::cir::StructType structTy = nullptr) { + return StructBuilder(static_cast(*this), nullptr, structTy); + } +}; + +class ConstantInitBuilder; +class ConstantStructBuilder; +class ConstantArrayBuilder; + +struct ConstantInitBuilderTraits { + using InitBuilder = ConstantInitBuilder; + using AggregateBuilderBase = ConstantAggregateBuilderBase; + using ArrayBuilder = ConstantArrayBuilder; + using StructBuilder = ConstantStructBuilder; +}; + +/// The standard implementation of ConstantInitBuilder used in Clang. +class ConstantInitBuilder + : public ConstantInitBuilderTemplateBase { +public: + explicit ConstantInitBuilder(CIRGenModule &CGM) + : ConstantInitBuilderTemplateBase(CGM) {} +}; + +/// A helper class of ConstantInitBuilder, used for building constant +/// array initializers. +class ConstantArrayBuilder + : public ConstantArrayBuilderTemplateBase { + template friend class ConstantInitBuilderTemplateBase; + + // The use of explicit qualification is a GCC workaround. + template + friend class cir::ConstantAggregateBuilderTemplateBase; + + ConstantArrayBuilder(ConstantInitBuilder &builder, + ConstantAggregateBuilderBase *parent, mlir::Type eltTy) + : ConstantArrayBuilderTemplateBase(builder, parent, eltTy) {} +}; + +/// A helper class of ConstantInitBuilder, used for building constant +/// struct initializers. +class ConstantStructBuilder + : public ConstantStructBuilderTemplateBase { + template friend class ConstantInitBuilderTemplateBase; + + // The use of explicit qualification is a GCC workaround. + template + friend class cir::ConstantAggregateBuilderTemplateBase; + + ConstantStructBuilder(ConstantInitBuilder &builder, + ConstantAggregateBuilderBase *parent, + mlir::cir::StructType structTy) + : ConstantStructBuilderTemplateBase(builder, parent, structTy) {} +}; + +} // end namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/ConstantInitFuture.h b/clang/lib/CIR/CodeGen/ConstantInitFuture.h new file mode 100644 index 000000000000..97631d5da88c --- /dev/null +++ b/clang/lib/CIR/CodeGen/ConstantInitFuture.h @@ -0,0 +1,102 @@ +//===- ConstantInitFuture.h - "Future" constant initializers ----*- C++ -*-===// +// +// 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 defines the ConstantInitFuture class. This is split out +// from ConstantInitBuilder.h in order to allow APIs to work with it +// without having to include that entire header. This is particularly +// important because it is often useful to be able to default-construct +// a future in, say, a default argument. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_CIR_CODEGEN_CONSTANTINITFUTURE_H +#define LLVM_CLANG_CIR_CODEGEN_CONSTANTINITFUTURE_H + +#include "mlir/IR/Attributes.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "llvm/ADT/PointerUnion.h" + +// Forward-declare ConstantInitBuilderBase and give it a +// PointerLikeTypeTraits specialization so that we can safely use it +// in a PointerUnion below. +namespace cir { +class ConstantInitBuilderBase; +} // namespace cir + +namespace llvm { +template <> struct PointerLikeTypeTraits<::cir::ConstantInitBuilderBase *> { + using T = ::cir::ConstantInitBuilderBase *; + + static inline void *getAsVoidPointer(T p) { return p; } + static inline T getFromVoidPointer(void *p) { return static_cast(p); } + static constexpr int NumLowBitsAvailable = 2; +}; +} // namespace llvm + +namespace cir { + +/// A "future" for a completed constant initializer, which can be passed +/// around independently of any sub-builders (but not the original parent). +class ConstantInitFuture { + using PairTy = llvm::PointerUnion; + + PairTy Data; + + friend class ConstantInitBuilderBase; + explicit ConstantInitFuture(ConstantInitBuilderBase *builder); + +public: + ConstantInitFuture() {} + + /// A future can be explicitly created from a fixed initializer. + explicit ConstantInitFuture(mlir::Attribute initializer) : Data(initializer) { + assert(initializer && "creating null future"); + } + + /// Is this future non-null? + explicit operator bool() const { return bool(Data); } + + /// Return the type of the initializer. + mlir::Type getType() const; + + /// Abandon this initializer. + void abandon(); + + /// Install the initializer into a global variable. This cannot + /// be called multiple times. + void installInGlobal(mlir::cir::GlobalOp global); + + void *getOpaqueValue() const { return Data.getOpaqueValue(); } + static ConstantInitFuture getFromOpaqueValue(void *value) { + ConstantInitFuture result; + result.Data = PairTy::getFromOpaqueValue(value); + return result; + } + static constexpr int NumLowBitsAvailable = + llvm::PointerLikeTypeTraits::NumLowBitsAvailable; +}; + +} // namespace cir + +namespace llvm { + +template <> struct PointerLikeTypeTraits<::cir::ConstantInitFuture> { + using T = ::cir::ConstantInitFuture; + + static inline void *getAsVoidPointer(T future) { + return future.getOpaqueValue(); + } + static inline T getFromVoidPointer(void *p) { + return T::getFromOpaqueValue(p); + } + static constexpr int NumLowBitsAvailable = T::NumLowBitsAvailable; +}; + +} // end namespace llvm + +#endif diff --git a/clang/lib/CIR/CodeGen/EHScopeStack.h b/clang/lib/CIR/CodeGen/EHScopeStack.h new file mode 100644 index 000000000000..8711cd3c232e --- /dev/null +++ b/clang/lib/CIR/CodeGen/EHScopeStack.h @@ -0,0 +1,426 @@ +//===-- EHScopeStack.h - Stack for cleanup CIR generation -------*- C++ -*-===// +// +// 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 should be the minimum interface required for other parts of +// CodeGen to emit cleanups. The implementation is in CIRGenCleanup.cpp and +// other implemenentation details that are not widely needed are in +// CIRGenCleanup.h. +// +// TODO(cir): this header should be shared between LLVM and CIR codegen. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIRGEN_EHSCOPESTACK_H +#define LLVM_CLANG_LIB_CIRGEN_EHSCOPESTACK_H + +#include "mlir/IR/Value.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" + +namespace cir { + +class CIRGenFunction; + +/// A branch fixup. These are required when emitting a goto to a +/// label which hasn't been emitted yet. The goto is optimistically +/// emitted as a branch to the basic block for the label, and (if it +/// occurs in a scope with non-trivial cleanups) a fixup is added to +/// the innermost cleanup. When a (normal) cleanup is popped, any +/// unresolved fixups in that scope are threaded through the cleanup. +struct BranchFixup { + // /// The block containing the terminator which needs to be modified + // /// into a switch if this fixup is resolved into the current scope. + // /// If null, LatestBranch points directly to the destination. + // llvm::BasicBlock *OptimisticBranchBlock; + + // /// The ultimate destination of the branch. + // /// + // /// This can be set to null to indicate that this fixup was + // /// successfully resolved. + // llvm::BasicBlock *Destination; + + // /// The destination index value. + // unsigned DestinationIndex; + + // /// The initial branch of the fixup. + // llvm::BranchInst *InitialBranch; +}; + +template struct InvariantValue { + typedef T type; + typedef T saved_type; + static bool needsSaving(type value) { return false; } + static saved_type save(CIRGenFunction &CGF, type value) { return value; } + static type restore(CIRGenFunction &CGF, saved_type value) { return value; } +}; + +/// A metaprogramming class for ensuring that a value will dominate an +/// arbitrary position in a function. +template struct DominatingValue : InvariantValue {}; + +template ::value || + std::is_base_of::value) && + !std::is_base_of::value && + !std::is_base_of::value> +struct DominatingPointer; +template struct DominatingPointer : InvariantValue {}; +// template struct DominatingPointer at end of file + +template struct DominatingValue : DominatingPointer {}; + +enum CleanupKind : unsigned { + /// Denotes a cleanup that should run when a scope is exited using exceptional + /// control flow (a throw statement leading to stack unwinding, ). + EHCleanup = 0x1, + + /// Denotes a cleanup that should run when a scope is exited using normal + /// control flow (falling off the end of the scope, return, goto, ...). + NormalCleanup = 0x2, + + NormalAndEHCleanup = EHCleanup | NormalCleanup, + + LifetimeMarker = 0x8, + NormalEHLifetimeMarker = LifetimeMarker | NormalAndEHCleanup, +}; + +/// A stack of scopes which respond to exceptions, including cleanups +/// and catch blocks. +class EHScopeStack { +public: + /* Should switch to alignof(uint64_t) instead of 8, when EHCleanupScope can */ + enum { ScopeStackAlignment = 8 }; + + /// A saved depth on the scope stack. This is necessary because + /// pushing scopes onto the stack invalidates iterators. + class stable_iterator { + friend class EHScopeStack; + + /// Offset from StartOfData to EndOfBuffer. + ptrdiff_t Size; + + stable_iterator(ptrdiff_t Size) : Size(Size) {} + + public: + static stable_iterator invalid() { return stable_iterator(-1); } + stable_iterator() : Size(-1) {} + + bool isValid() const { return Size >= 0; } + + /// Returns true if this scope encloses I. + /// Returns false if I is invalid. + /// This scope must be valid. + bool encloses(stable_iterator I) const { return Size <= I.Size; } + + /// Returns true if this scope strictly encloses I: that is, + /// if it encloses I and is not I. + /// Returns false is I is invalid. + /// This scope must be valid. + bool strictlyEncloses(stable_iterator I) const { return Size < I.Size; } + + friend bool operator==(stable_iterator A, stable_iterator B) { + return A.Size == B.Size; + } + friend bool operator!=(stable_iterator A, stable_iterator B) { + return A.Size != B.Size; + } + }; + + /// Information for lazily generating a cleanup. Subclasses must be + /// POD-like: cleanups will not be destructed, and they will be + /// allocated on the cleanup stack and freely copied and moved + /// around. + /// + /// Cleanup implementations should generally be declared in an + /// anonymous namespace. + class Cleanup { + // Anchor the construction vtable. + virtual void anchor(); + + protected: + ~Cleanup() = default; + + public: + Cleanup(const Cleanup &) = default; + Cleanup(Cleanup &&) {} + Cleanup() = default; + + virtual bool isRedundantBeforeReturn() { return false; } + + /// Generation flags. + class Flags { + enum { + F_IsForEH = 0x1, + F_IsNormalCleanupKind = 0x2, + F_IsEHCleanupKind = 0x4, + F_HasExitSwitch = 0x8, + }; + unsigned flags; + + public: + Flags() : flags(0) {} + + /// isForEH - true if the current emission is for an EH cleanup. + bool isForEHCleanup() const { return flags & F_IsForEH; } + bool isForNormalCleanup() const { return !isForEHCleanup(); } + void setIsForEHCleanup() { flags |= F_IsForEH; } + + bool isNormalCleanupKind() const { return flags & F_IsNormalCleanupKind; } + void setIsNormalCleanupKind() { flags |= F_IsNormalCleanupKind; } + + /// isEHCleanupKind - true if the cleanup was pushed as an EH + /// cleanup. + bool isEHCleanupKind() const { return flags & F_IsEHCleanupKind; } + void setIsEHCleanupKind() { flags |= F_IsEHCleanupKind; } + + bool hasExitSwitch() const { return flags & F_HasExitSwitch; } + void setHasExitSwitch() { flags |= F_HasExitSwitch; } + }; + + /// Emit the cleanup. For normal cleanups, this is run in the + /// same EH context as when the cleanup was pushed, i.e. the + /// immediately-enclosing context of the cleanup scope. For + /// EH cleanups, this is run in a terminate context. + /// + // \param flags cleanup kind. + virtual void Emit(CIRGenFunction &CGF, Flags flags) = 0; + }; + + /// ConditionalCleanup stores the saved form of its parameters, + /// then restores them and performs the cleanup. + template + class ConditionalCleanup final : public Cleanup { + typedef std::tuple::saved_type...> SavedTuple; + SavedTuple Saved; + + template + T restore(CIRGenFunction &CGF, std::index_sequence) { + // It's important that the restores are emitted in order. The braced init + // list guarantees that. + return T{DominatingValue::restore(CGF, std::get(Saved))...}; + } + + void Emit(CIRGenFunction &CGF, Flags flags) override { + restore(CGF, std::index_sequence_for()).Emit(CGF, flags); + } + + public: + ConditionalCleanup(typename DominatingValue::saved_type... A) + : Saved(A...) {} + + ConditionalCleanup(SavedTuple Tuple) : Saved(std::move(Tuple)) {} + }; + +private: + // The implementation for this class is in CGException.h and + // CGException.cpp; the definition is here because it's used as a + // member of CIRGenFunction. + + /// The start of the scope-stack buffer, i.e. the allocated pointer + /// for the buffer. All of these pointers are either simultaneously + /// null or simultaneously valid. + char *StartOfBuffer; + + /// The end of the buffer. + char *EndOfBuffer; + + /// The first valid entry in the buffer. + char *StartOfData; + + /// The innermost normal cleanup on the stack. + stable_iterator InnermostNormalCleanup; + + /// The innermost EH scope on the stack. + stable_iterator InnermostEHScope; + + /// The CGF this Stack belong to + CIRGenFunction *CGF; + + /// The current set of branch fixups. A branch fixup is a jump to + /// an as-yet unemitted label, i.e. a label for which we don't yet + /// know the EH stack depth. Whenever we pop a cleanup, we have + /// to thread all the current branch fixups through it. + /// + /// Fixups are recorded as the Use of the respective branch or + /// switch statement. The use points to the final destination. + /// When popping out of a cleanup, these uses are threaded through + /// the cleanup and adjusted to point to the new cleanup. + /// + /// Note that branches are allowed to jump into protected scopes + /// in certain situations; e.g. the following code is legal: + /// struct A { ~A(); }; // trivial ctor, non-trivial dtor + /// goto foo; + /// A a; + /// foo: + /// bar(); + llvm::SmallVector BranchFixups; + + char *allocate(size_t Size); + void deallocate(size_t Size); + + void *pushCleanup(CleanupKind K, size_t DataSize); + +public: + EHScopeStack() + : StartOfBuffer(nullptr), EndOfBuffer(nullptr), StartOfData(nullptr), + InnermostNormalCleanup(stable_end()), InnermostEHScope(stable_end()), + CGF(nullptr) {} + ~EHScopeStack() { delete[] StartOfBuffer; } + + /// Push a lazily-created cleanup on the stack. + template void pushCleanup(CleanupKind Kind, As... A) { + static_assert(alignof(T) <= ScopeStackAlignment, + "Cleanup's alignment is too large."); + void *Buffer = pushCleanup(Kind, sizeof(T)); + Cleanup *Obj = new (Buffer) T(A...); + (void) Obj; + } + + /// Push a lazily-created cleanup on the stack. Tuple version. + template + void pushCleanupTuple(CleanupKind Kind, std::tuple A) { + static_assert(alignof(T) <= ScopeStackAlignment, + "Cleanup's alignment is too large."); + void *Buffer = pushCleanup(Kind, sizeof(T)); + Cleanup *Obj = new (Buffer) T(std::move(A)); + (void) Obj; + } + + // Feel free to add more variants of the following: + + /// Push a cleanup with non-constant storage requirements on the + /// stack. The cleanup type must provide an additional static method: + /// static size_t getExtraSize(size_t); + /// The argument to this method will be the value N, which will also + /// be passed as the first argument to the constructor. + /// + /// The data stored in the extra storage must obey the same + /// restrictions as normal cleanup member data. + /// + /// The pointer returned from this method is valid until the cleanup + /// stack is modified. + template + T *pushCleanupWithExtra(CleanupKind Kind, size_t N, As... A) { + static_assert(alignof(T) <= ScopeStackAlignment, + "Cleanup's alignment is too large."); + void *Buffer = pushCleanup(Kind, sizeof(T) + T::getExtraSize(N)); + return new (Buffer) T(N, A...); + } + + void pushCopyOfCleanup(CleanupKind Kind, const void *Cleanup, size_t Size) { + void *Buffer = pushCleanup(Kind, Size); + std::memcpy(Buffer, Cleanup, Size); + } + + void setCGF(CIRGenFunction *inCGF) { CGF = inCGF; } + + /// Pops a cleanup scope off the stack. This is private to CGCleanup.cpp. + void popCleanup(); + + /// Push a set of catch handlers on the stack. The catch is + /// uninitialized and will need to have the given number of handlers + /// set on it. + class EHCatchScope *pushCatch(unsigned NumHandlers); + + /// Pops a catch scope off the stack. This is private to CGException.cpp. + void popCatch(); + + /// Push an exceptions filter on the stack. + class EHFilterScope *pushFilter(unsigned NumFilters); + + /// Pops an exceptions filter off the stack. + void popFilter(); + + /// Push a terminate handler on the stack. + void pushTerminate(); + + /// Pops a terminate handler off the stack. + void popTerminate(); + + // Returns true iff the current scope is either empty or contains only + // lifetime markers, i.e. no real cleanup code + bool containsOnlyLifetimeMarkers(stable_iterator Old) const; + + /// Determines whether the exception-scopes stack is empty. + bool empty() const { return StartOfData == EndOfBuffer; } + + bool requiresLandingPad() const; + + /// Determines whether there are any normal cleanups on the stack. + bool hasNormalCleanups() const { + return InnermostNormalCleanup != stable_end(); + } + + /// Returns the innermost normal cleanup on the stack, or + /// stable_end() if there are no normal cleanups. + stable_iterator getInnermostNormalCleanup() const { + return InnermostNormalCleanup; + } + stable_iterator getInnermostActiveNormalCleanup() const; + + stable_iterator getInnermostEHScope() const { + return InnermostEHScope; + } + + + /// An unstable reference to a scope-stack depth. Invalidated by + /// pushes but not pops. + class iterator; + + /// Returns an iterator pointing to the innermost EH scope. + iterator begin() const; + + /// Returns an iterator pointing to the outermost EH scope. + iterator end() const; + + /// Create a stable reference to the top of the EH stack. The + /// returned reference is valid until that scope is popped off the + /// stack. + stable_iterator stable_begin() const { + return stable_iterator(EndOfBuffer - StartOfData); + } + + /// Create a stable reference to the bottom of the EH stack. + static stable_iterator stable_end() { + return stable_iterator(0); + } + + /// Translates an iterator into a stable_iterator. + stable_iterator stabilize(iterator it) const; + + /// Turn a stable reference to a scope depth into a unstable pointer + /// to the EH stack. + iterator find(stable_iterator save) const; + + /// Add a branch fixup to the current cleanup scope. + BranchFixup &addBranchFixup() { + assert(hasNormalCleanups() && "adding fixup in scope without cleanups"); + BranchFixups.push_back(BranchFixup()); + return BranchFixups.back(); + } + + unsigned getNumBranchFixups() const { return BranchFixups.size(); } + BranchFixup &getBranchFixup(unsigned I) { + assert(I < getNumBranchFixups()); + return BranchFixups[I]; + } + + /// Pops lazily-removed fixups from the end of the list. This + /// should only be called by procedures which have just popped a + /// cleanup or resolved one or more fixups. + void popNullFixups(); + + /// Clears the branch-fixups list. This should only be called by + /// ResolveAllBranchFixups. + void clearFixups() { BranchFixups.clear(); } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/TargetInfo.cpp b/clang/lib/CIR/CodeGen/TargetInfo.cpp new file mode 100644 index 000000000000..dc5ab92b4121 --- /dev/null +++ b/clang/lib/CIR/CodeGen/TargetInfo.cpp @@ -0,0 +1,460 @@ +#include "TargetInfo.h" +#include "ABIInfo.h" +#include "CIRGenCXXABI.h" +#include "CIRGenFunctionInfo.h" +#include "CIRGenTypes.h" +#include "CallingConv.h" + +#include "clang/Basic/TargetInfo.h" + +using namespace cir; +using namespace clang; + +static bool testIfIsVoidTy(QualType Ty) { + const auto *BT = Ty->getAs(); + if (!BT) + return false; + + BuiltinType::Kind k = BT->getKind(); + return k == BuiltinType::Void; +} + +//===----------------------------------------------------------------------===// +// AArch64 ABI Implementation +//===----------------------------------------------------------------------===// + +namespace { + +class AArch64ABIInfo : public ABIInfo { +public: + enum ABIKind { AAPCS = 0, DarwinPCS, Win64 }; + +private: + ABIKind Kind; + +public: + AArch64ABIInfo(CIRGenTypes &CGT, ABIKind Kind) : ABIInfo(CGT), Kind(Kind) {} + +private: + ABIKind getABIKind() const { return Kind; } + bool isDarwinPCS() const { return Kind == DarwinPCS; } + + ABIArgInfo classifyReturnType(QualType RetTy, bool IsVariadic) const; + ABIArgInfo classifyArgumentType(QualType RetTy, bool IsVariadic, + unsigned CallingConvention) const; + + void computeInfo(CIRGenFunctionInfo &FI) const override { + // Top leevl CIR has unlimited arguments and return types. Lowering for ABI + // specific concerns should happen during a lowering phase. Assume + // everything is direct for now. + for (CIRGenFunctionInfo::arg_iterator it = FI.arg_begin(), + ie = FI.arg_end(); + it != ie; ++it) { + if (testIfIsVoidTy(it->type)) + it->info = ABIArgInfo::getIgnore(); + else + it->info = ABIArgInfo::getDirect(CGT.ConvertType(it->type)); + } + auto RetTy = FI.getReturnType(); + if (testIfIsVoidTy(RetTy)) + FI.getReturnInfo() = ABIArgInfo::getIgnore(); + else + FI.getReturnInfo() = ABIArgInfo::getDirect(CGT.ConvertType(RetTy)); + + return; + } +}; + +class AArch64TargetCIRGenInfo : public TargetCIRGenInfo { +public: + AArch64TargetCIRGenInfo(CIRGenTypes &CGT, AArch64ABIInfo::ABIKind Kind) + : TargetCIRGenInfo(std::make_unique(CGT, Kind)) {} +}; + +} // namespace + +namespace { + +/// The AVX ABI leel for X86 targets. +enum class X86AVXABILevel { None, AVX, AVX512 }; + +class X86_64ABIInfo : public ABIInfo { + enum Class { + Integer = 0, + SSE, + SSEUp, + X87, + X87Up, + ComplexX87, + NoClass, + Memory + }; + + // X86AVXABILevel AVXLevel; + // Some ABIs (e.g. X32 ABI and Native Client OS) use 32 bit pointers on 64-bit + // hardware. + // bool Has64BitPointers; + +public: + X86_64ABIInfo(CIRGenTypes &CGT, X86AVXABILevel AVXLevel) + : ABIInfo(CGT) + // , AVXLevel(AVXLevel) + // , Has64BitPointers(CGT.getDataLayout().getPointeSize(0) == 8) + {} + + virtual void computeInfo(CIRGenFunctionInfo &FI) const override; + + /// classify - Determine the x86_64 register classes in which the given type T + /// should be passed. + /// + /// \param Lo - The classification for the parts of the type residing in the + /// low word of the containing object. + /// + /// \param Hi - The classification for the parts of the type residing in the + /// high word of the containing object. + /// + /// \param OffsetBase - The bit offset of this type in the containing object. + /// Some parameters are classified different depending on whether they + /// straddle an eightbyte boundary. + /// + /// \param isNamedArg - Whether the argument in question is a "named" + /// argument, as used in AMD64-ABI 3.5.7. + /// + /// If a word is unused its result will be NoClass; if a type should be passed + /// in Memory then at least the classification of \arg Lo will be Memory. + /// + /// The \arg Lo class will be NoClass iff the argument is ignored. + /// + /// If the \arg Lo class is ComplexX87, then the \arg Hi class will also be + /// ComplexX87. + void classify(clang::QualType T, uint64_t OffsetBase, Class &Lo, Class &Hi, + bool isNamedArg) const; + + mlir::Type GetSSETypeAtOffset(mlir::Type CIRType, unsigned CIROffset, + clang::QualType SourceTy, + unsigned SourceOffset) const; + + ABIArgInfo classifyReturnType(QualType RetTy) const; + + ABIArgInfo classifyArgumentType(clang::QualType Ty, unsigned freeIntRegs, + unsigned &neededInt, unsigned &neededSSE, + bool isNamedArg) const; + + mlir::Type GetINTEGERTypeAtOffset(mlir::Type CIRType, unsigned CIROffset, + QualType SourceTy, + unsigned SourceOffset) const; + + /// getIndirectResult - Give a source type \arg Ty, return a suitable result + /// such that the argument will be passed in memory. + /// + /// \param freeIntRegs - The number of free integer registers remaining + /// available. + ABIArgInfo getIndirectResult(QualType Ty, unsigned freeIntRegs) const; +}; + +class X86_64TargetCIRGenInfo : public TargetCIRGenInfo { +public: + X86_64TargetCIRGenInfo(CIRGenTypes &CGT, X86AVXABILevel AVXLevel) + : TargetCIRGenInfo(std::make_unique(CGT, AVXLevel)) {} +}; +} // namespace + +// TODO(cir): remove the attribute once this gets used. +LLVM_ATTRIBUTE_UNUSED +static bool classifyReturnType(const CIRGenCXXABI &CXXABI, + CIRGenFunctionInfo &FI, const ABIInfo &Info) { + QualType Ty = FI.getReturnType(); + + assert(!Ty->getAs() && "RecordType returns NYI"); + + return CXXABI.classifyReturnType(FI); +} + +CIRGenCXXABI &ABIInfo::getCXXABI() const { return CGT.getCXXABI(); } + +clang::ASTContext &ABIInfo::getContext() const { return CGT.getContext(); } + +ABIArgInfo X86_64ABIInfo::getIndirectResult(QualType Ty, + unsigned freeIntRegs) const { + assert(false && "NYI"); +} + +void X86_64ABIInfo::computeInfo(CIRGenFunctionInfo &FI) const { + // Top level CIR has unlimited arguments and return types. Lowering for ABI + // specific concerns should happen during a lowering phase. Assume everything + // is direct for now. + for (CIRGenFunctionInfo::arg_iterator it = FI.arg_begin(), ie = FI.arg_end(); + it != ie; ++it) { + if (testIfIsVoidTy(it->type)) + it->info = ABIArgInfo::getIgnore(); + else + it->info = ABIArgInfo::getDirect(CGT.ConvertType(it->type)); + } + auto RetTy = FI.getReturnType(); + if (testIfIsVoidTy(RetTy)) + FI.getReturnInfo() = ABIArgInfo::getIgnore(); + else + FI.getReturnInfo() = ABIArgInfo::getDirect(CGT.ConvertType(RetTy)); +} + +/// Pass transparent unions as if they were the type of the first element. Sema +/// should ensure that all elements of the union have the same "machine type". +static QualType useFirstFieldIfTransparentUnion(QualType Ty) { + assert(!Ty->getAsUnionType() && "NYI"); + return Ty; +} + +/// GetINTEGERTypeAtOffset - The ABI specifies that a value should be passed in +/// an 8-byte GPR. This means that we either have a scalar or we are talking +/// about the high or low part of an up-to-16-byte struct. This routine picks +/// the best CIR type to represent this, which may be i64 or may be anything +/// else that the backend will pass in a GPR that works better (e.g. i8, %foo*, +/// etc). +/// +/// PrefType is a CIR type that corresponds to (part of) the IR type for the +/// source type. CIROffset is an offset in bytes into the CIR type taht the +/// 8-byte value references. PrefType may be null. +/// +/// SourceTy is the source-level type for the entire argument. SourceOffset is +/// an offset into this that we're processing (which is always either 0 or 8). +/// +mlir::Type X86_64ABIInfo::GetINTEGERTypeAtOffset(mlir::Type CIRType, + unsigned CIROffset, + QualType SourceTy, + unsigned SourceOffset) const { + // TODO: entirely stubbed out + assert(CIROffset == 0 && "NYI"); + assert(SourceOffset == 0 && "NYI"); + return CIRType; +} + +ABIArgInfo X86_64ABIInfo::classifyArgumentType(QualType Ty, + unsigned int freeIntRegs, + unsigned int &neededInt, + unsigned int &neededSSE, + bool isNamedArg) const { + Ty = useFirstFieldIfTransparentUnion(Ty); + + X86_64ABIInfo::Class Lo, Hi; + classify(Ty, 0, Lo, Hi, isNamedArg); + + // Check some invariants + // FIXME: Enforce these by construction. + assert((Hi != Memory || Lo == Memory) && "Invalid memory classification."); + assert((Hi != SSEUp || Lo == SSE) && "Invalid SSEUp classification."); + + neededInt = 0; + neededSSE = 0; + mlir::Type ResType = nullptr; + switch (Lo) { + default: + assert(false && "NYI"); + + // AMD64-ABI 3.2.3p3: Rule 2. If the class is INTEGER, the next available + // register of the sequence %rdi, %rsi, %rdx, %rcx, %r8 and %r9 is used. + case Integer: + ++neededInt; + + // Pick an 8-byte type based on the preferred type. + ResType = GetINTEGERTypeAtOffset(CGT.ConvertType(Ty), 0, Ty, 0); + + // If we have a sign or zero extended integer, make sure to return Extend so + // that the parameter gets the right LLVM IR attributes. + if (Hi == NoClass && ResType.isa()) { + assert(!Ty->getAs() && "NYI"); + if (Ty->isSignedIntegerOrEnumerationType() && + isPromotableIntegerTypeForABI(Ty)) + return ABIArgInfo::getExtend(Ty); + } + + break; + + // AMD64-ABI 3.2.3p3: Rule 3. If the class is SSE, the next available SSE + // register is used, the registers are taken in the order from %xmm0 to + // %xmm7. + case SSE: { + mlir::Type CIRType = CGT.ConvertType(Ty); + ResType = GetSSETypeAtOffset(CIRType, 0, Ty, 0); + ++neededSSE; + break; + } + } + + mlir::Type HighPart = nullptr; + switch (Hi) { + default: + assert(false && "NYI"); + case NoClass: + break; + } + + assert(!HighPart && "NYI"); + + return ABIArgInfo::getDirect(ResType); +} + +ABIInfo::~ABIInfo() {} + +bool ABIInfo::isPromotableIntegerTypeForABI(QualType Ty) const { + if (getContext().isPromotableIntegerType(Ty)) + return true; + + assert(!Ty->getAs() && "NYI"); + + return false; +} + +void X86_64ABIInfo::classify(QualType Ty, uint64_t OffsetBase, Class &Lo, + Class &Hi, bool isNamedArg) const { + // FIXME: This code can be simplified by introducing a simple value class for + // Class pairs with appropriate constructor methods for the various + // situations. + + // FIXME: Some of the split computations are wrong; unaligned vectors + // shouldn't be passed in registers for example, so there is no chance they + // can straddle an eightbyte. Verify & simplify. + + Lo = Hi = NoClass; + Class &Current = OffsetBase < 64 ? Lo : Hi; + Current = Memory; + + if (const auto *BT = Ty->getAs()) { + BuiltinType::Kind k = BT->getKind(); + if (k == BuiltinType::Void) { + Current = NoClass; + } else if (k == BuiltinType::Int128 || k == BuiltinType::UInt128) { + assert(false && "NYI"); + Lo = Integer; + Hi = Integer; + } else if (k >= BuiltinType::Bool && k <= BuiltinType::LongLong) { + Current = Integer; + } else if (k == BuiltinType::Float || k == BuiltinType::Double || + k == BuiltinType::Float16) { + Current = SSE; + } else if (k == BuiltinType::LongDouble) { + assert(false && "NYI"); + } else + assert(false && + "Only void and Integer supported so far for builtin types"); + // FIXME: _Decimal32 and _Decimal64 are SSE. + // FIXME: _float128 and _Decimal128 are (SSE, SSEUp). + return; + } + + assert(!Ty->getAs() && "Enums NYI"); + if (Ty->hasPointerRepresentation()) { + Current = Integer; + return; + } + + assert(false && "Nothing else implemented yet"); +} + +/// GetSSETypeAtOffset - Return a type that will be passed by the backend in the +/// low 8 bytes of an XMM register, corresponding to the SSE class. +mlir::Type X86_64ABIInfo::GetSSETypeAtOffset(mlir::Type CIRType, + unsigned int CIROffset, + clang::QualType SourceTy, + unsigned int SourceOffset) const { + // TODO: entirely stubbed out + assert(CIROffset == 0 && "NYI"); + assert(SourceOffset == 0 && "NYI"); + return CIRType; +} + +ABIArgInfo X86_64ABIInfo::classifyReturnType(QualType RetTy) const { + // AMD64-ABI 3.2.3p4: Rule 1. Classify the return type with the classification + // algorithm. + X86_64ABIInfo::Class Lo, Hi; + classify(RetTy, 0, Lo, Hi, /*isNamedArg*/ true); + + // Check some invariants. + assert((Hi != Memory || Lo == Memory) && "Invalid memory classification."); + assert((Hi != SSEUp || Lo == SSE) && "Invalid SSEUp classification."); + + mlir::Type ResType = nullptr; + assert(Lo == NoClass || Lo == Integer || + Lo == SSE && "Only NoClass and Integer supported so far"); + + switch (Lo) { + case NoClass: + assert(Hi == NoClass && "Only NoClass supported so far for Hi"); + return ABIArgInfo::getIgnore(); + + // AMD64-ABI 3.2.3p4: Rule 3. If the class is INTEGER, the next available + // register of the sequence %rax, %rdx is used. + case Integer: + ResType = GetINTEGERTypeAtOffset(CGT.ConvertType(RetTy), 0, RetTy, 0); + + // If we have a sign or zero extended integer, make sure to return Extend so + // that the parameter gets the right LLVM IR attributes. + // TODO: extend the above consideration to MLIR + if (Hi == NoClass && ResType.isa()) { + // Treat an enum type as its underlying type. + if (const auto *EnumTy = RetTy->getAs()) + RetTy = EnumTy->getDecl()->getIntegerType(); + + if (RetTy->isIntegralOrEnumerationType() && + isPromotableIntegerTypeForABI(RetTy)) { + return ABIArgInfo::getExtend(RetTy); + } + } + break; + + // AMD64-ABI 3.2.3p4: Rule 4. If the class is SSE, the next available SSE + // register of the sequence %xmm0, %xmm1 is used. + case SSE: + ResType = GetSSETypeAtOffset(CGT.ConvertType(RetTy), 0, RetTy, 0); + break; + + default: + llvm_unreachable("NYI"); + } + + mlir::Type HighPart = nullptr; + + if (HighPart) + assert(false && "NYI"); + + return ABIArgInfo::getDirect(ResType); +} + +const TargetCIRGenInfo &CIRGenModule::getTargetCIRGenInfo() { + if (TheTargetCIRGenInfo) + return *TheTargetCIRGenInfo; + + // Helper to set the unique_ptr while still keeping the return value. + auto SetCIRGenInfo = [&](TargetCIRGenInfo *P) -> const TargetCIRGenInfo & { + this->TheTargetCIRGenInfo.reset(P); + return *P; + }; + + const llvm::Triple &Triple = getTarget().getTriple(); + + switch (Triple.getArch()) { + default: + assert(false && "Target not yet supported!"); + case llvm::Triple::aarch64: { + AArch64ABIInfo::ABIKind Kind = AArch64ABIInfo::AAPCS; + assert(getTarget().getABI() == "aapcs" || + getTarget().getABI() == "darwinpcs" && + "Only Darwin supported for aarch64"); + Kind = AArch64ABIInfo::DarwinPCS; + return SetCIRGenInfo(new AArch64TargetCIRGenInfo(genTypes, Kind)); + } + + case llvm::Triple::x86_64: { + StringRef ABI = getTarget().getABI(); + X86AVXABILevel AVXLevel = (ABI == "avx512" ? X86AVXABILevel::AVX512 + : ABI == "avx" ? X86AVXABILevel::AVX + : X86AVXABILevel::None); + + switch (Triple.getOS()) { + default: + assert(false && "OSType NYI"); + case llvm::Triple::Linux: + return SetCIRGenInfo(new X86_64TargetCIRGenInfo(genTypes, AVXLevel)); + } + } + } +} diff --git a/clang/lib/CIR/CodeGen/TargetInfo.h b/clang/lib/CIR/CodeGen/TargetInfo.h new file mode 100644 index 000000000000..a06f59052302 --- /dev/null +++ b/clang/lib/CIR/CodeGen/TargetInfo.h @@ -0,0 +1,38 @@ +//===---- TargetInfo.h - Encapsulate target details -------------*- C++ -*-===// +// +// 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 wrap the information about a call or function +// definition used to handle ABI compliancy. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_TARGETINFO_H +#define LLVM_CLANG_LIB_CIR_TARGETINFO_H + +#include "ABIInfo.h" + +#include + +namespace cir { + +/// This class organizes various target-specific codegeneration issues, like +/// target-specific attributes, builtins and so on. +/// Equivalent to LLVM's TargetCodeGenInfo. +class TargetCIRGenInfo { + std::unique_ptr Info = nullptr; + +public: + TargetCIRGenInfo(std::unique_ptr Info) : Info(std::move(Info)) {} + + /// Returns ABI info helper for the target. + const ABIInfo &getABIInfo() const { return *Info; } +}; + +} // namespace cir + +#endif diff --git a/clang/lib/CIR/CodeGen/UnimplementedFeatureGuarding.h b/clang/lib/CIR/CodeGen/UnimplementedFeatureGuarding.h new file mode 100644 index 000000000000..5a857a2db39f --- /dev/null +++ b/clang/lib/CIR/CodeGen/UnimplementedFeatureGuarding.h @@ -0,0 +1,145 @@ +//===---- UnimplementedFeatureGuarding.h - Checks against NYI ---*- C++ -*-===// +// +// 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 introduces some helper classes to guard against features that +// CodeGen supports that we do not have and also do not have great ways to +// assert against. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_CIR_UFG +#define LLVM_CLANG_LIB_CIR_UFG + +namespace cir { +struct UnimplementedFeature { + // TODO(CIR): Implement the CIRGenFunction::buildTypeCheck method that handles + // sanitizer related type check features + static bool buildTypeCheck() { return false; } + static bool tbaa() { return false; } + static bool cleanups() { return false; } + // This is for whether or not we've implemented a cir::VectorType + // corresponding to `llvm::VectorType` + static bool cirVectorType() { return false; } + + // Address space related + static bool addressSpace() { return false; } + static bool addressSpaceInGlobalVar() { return false; } + static bool getASTAllocaAddressSpace() { return false; } + + // Clang codegen options + static bool strictVTablePointers() { return false; } + + // Unhandled global/linkage information. + static bool unnamedAddr() { return false; } + static bool setComdat() { return false; } + static bool setGlobalVarSection() { return false; } + static bool setDSOLocal() { return false; } + static bool threadLocal() { return false; } + static bool setDLLStorageClass() { return false; } + static bool setDLLImportDLLExport() { return false; } + static bool setPartition() { return false; } + static bool setGlobalVisibility() { return false; } + static bool hiddenVisibility() { return false; } + static bool protectedVisibility() { return false; } + static bool addCompilerUsedGlobal() { return false; } + + // Sanitizers + static bool reportGlobalToASan() { return false; } + static bool emitAsanPrologueOrEpilogue() { return false; } + static bool emitCheckedInBoundsGEP() { return false; } + static bool pointerOverflowSanitizer() { return false; } + + // ObjC + static bool setObjCGCLValueClass() { return false; } + + // Debug info + static bool generateDebugInfo() { return false; } + + // LLVM Attributes + static bool attributeBuiltin() { return false; } + static bool attributeNoBuiltin() { return false; } + static bool parameterAttributes() { return false; } + + // Coroutines + static bool unhandledException() { return false; } + + // Missing Emissions + static bool variablyModifiedTypeEmission() { return false; } + static bool buildLValueAlignmentAssumption() { return false; } + static bool buildDerivedToBaseCastForDevirt() { return false; } + + // Data layout + static bool dataLayoutGetIndexTypeSizeInBits() { return false; } + + // References related stuff + static bool ARC() { return false; } // Automatic reference counting + + // Clang early optimizations or things defered to LLVM lowering. + static bool shouldUseBZeroPlusStoresToInitialize() { return false; } + static bool shouldUseMemSetToInitialize() { return false; } + static bool shouldSplitConstantStore() { return false; } + static bool shouldCreateMemCpyFromGlobal() { return false; } + static bool shouldReverseUnaryCondOnBoolExpr() { return false; } + static bool fieldMemcpyizerBuildMemcpy() { return false; } + static bool isTrivialAndisDefaultConstructor() { return false; } + static bool isMemcpyEquivalentSpecialMember() { return false; } + static bool constructABIArgDirectExtend() { return false; } + static bool mayHaveIntegerOverflow() { return false; } + static bool llvmLoweringPtrDiffConsidersPointee() { return false; } + + // Folding methods. + static bool foldBinOpFMF() { return false; } + + // Fast math. + static bool fastMathGuard() { return false; } + static bool fastMathFlags() { return false; } + static bool fastMathFuncAttributes() { return false; } + + // Type qualifiers. + static bool atomicTypes() { return false; } + static bool volatileTypes() { return false; } + + static bool capturedByInit() { return false; } + static bool tryEmitAsConstant() { return false; } + static bool incrementProfileCounter() { return false; } + static bool createProfileWeightsForLoop() { return false; } + static bool emitCondLikelihoodViaExpectIntrinsic() { return false; } + static bool requiresReturnValueCheck() { return false; } + static bool shouldEmitLifetimeMarkers() { return false; } + static bool peepholeProtection() { return false; } + static bool CGCapturedStmtInfo() { return false; } + static bool cxxABI() { return false; } + static bool openCL() { return false; } + static bool openMP() { return false; } + static bool ehStack() { return false; } + static bool isVarArg() { return false; } + static bool setNonGC() { return false; } + static bool volatileLoadOrStore() { return false; } + static bool armComputeVolatileBitfields() { return false; } + static bool setCommonAttributes() { return false; } + static bool insertBuiltinUnpredictable() { return false; } + static bool createInvariantGroup() { return false; } + static bool addAutoInitAnnotation() { return false; } + static bool addHeapAllocSiteMetadata() { return false; } + static bool loopInfoStack() { return false; } + static bool requiresCleanups() { return false; } + static bool constantFoldsToSimpleInteger() { return false; } + static bool alignedLoad() { return false; } + static bool checkFunctionCallABI() { return false; } + static bool zeroInitializer() { return false; } + static bool targetCodeGenInfoIsProtoCallVariadic() { return false; } + static bool chainCalls() { return false; } + static bool operandBundles() { return false; } + static bool exceptions() { return false; } + static bool metaDataNode() { return false; } + static bool isSEHTryScope() { return false; } + static bool emitScalarRangeCheck() { return false; } +}; +} // namespace cir + +#endif diff --git a/clang/lib/CIR/Dialect/CMakeLists.txt b/clang/lib/CIR/Dialect/CMakeLists.txt new file mode 100644 index 000000000000..9f57627c321f --- /dev/null +++ b/clang/lib/CIR/Dialect/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(IR) +add_subdirectory(Transforms) diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp new file mode 100644 index 000000000000..8d7b63d787e3 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp @@ -0,0 +1,306 @@ +//===- CIRTypes.cpp - MLIR CIR Types --------------------------------------===// +// +// 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 defines the types in the CIR dialect. +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinAttributeInterfaces.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/Support/LLVM.h" +#include "mlir/Support/LogicalResult.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/TypeSwitch.h" + +// ClangIR holds back AST references when available. +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" + +static void printStructMembers(mlir::AsmPrinter &p, mlir::ArrayAttr members); +static mlir::ParseResult parseStructMembers(::mlir::AsmParser &parser, + mlir::ArrayAttr &members); + +#define GET_ATTRDEF_CLASSES +#include "clang/CIR/Dialect/IR/CIROpsAttributes.cpp.inc" + +using namespace mlir; +using namespace mlir::cir; + +//===----------------------------------------------------------------------===// +// CIR AST Attr helpers +//===----------------------------------------------------------------------===// + +namespace mlir { +namespace cir { + +mlir::Attribute makeFuncDeclAttr(const clang::Decl *decl, + mlir::MLIRContext *ctx) { + return llvm::TypeSwitch(decl) + .Case([ctx](const clang::CXXConstructorDecl *ast) { + return ASTCXXConstructorDeclAttr::get(ctx, ast); + }) + .Case([ctx](const clang::CXXConversionDecl *ast) { + return ASTCXXConversionDeclAttr::get(ctx, ast); + }) + .Case([ctx](const clang::CXXDestructorDecl *ast) { + return ASTCXXDestructorDeclAttr::get(ctx, ast); + }) + .Case([ctx](const clang::CXXMethodDecl *ast) { + return ASTCXXMethodDeclAttr::get(ctx, ast); + }) + .Case([ctx](const clang::FunctionDecl *ast) { + return ASTFunctionDeclAttr::get(ctx, ast); + }) + .Default([](auto) { + llvm_unreachable("unexpected Decl kind"); + return mlir::Attribute(); + }); +} + +} // namespace cir +} // namespace mlir + +//===----------------------------------------------------------------------===// +// General CIR parsing / printing +//===----------------------------------------------------------------------===// + +Attribute CIRDialect::parseAttribute(DialectAsmParser &parser, + Type type) const { + llvm::SMLoc typeLoc = parser.getCurrentLocation(); + StringRef mnemonic; + Attribute genAttr; + OptionalParseResult parseResult = + generatedAttributeParser(parser, &mnemonic, type, genAttr); + if (parseResult.has_value()) + return genAttr; + parser.emitError(typeLoc, "unknown attribute in CIR dialect"); + return Attribute(); +} + +void CIRDialect::printAttribute(Attribute attr, DialectAsmPrinter &os) const { + if (failed(generatedAttributePrinter(attr, os))) + llvm_unreachable("unexpected CIR type kind"); +} + +static void printStructMembers(mlir::AsmPrinter &printer, + mlir::ArrayAttr members) { + printer << '{'; + llvm::interleaveComma(members, printer); + printer << '}'; +} + +static ParseResult parseStructMembers(mlir::AsmParser &parser, + mlir::ArrayAttr &members) { + SmallVector elts; + + auto delimiter = AsmParser::Delimiter::Braces; + auto result = parser.parseCommaSeparatedList(delimiter, [&]() { + mlir::TypedAttr attr; + if (parser.parseAttribute(attr).failed()) + return mlir::failure(); + elts.push_back(attr); + return mlir::success(); + }); + + if (result.failed()) + return mlir::failure(); + + members = mlir::ArrayAttr::get(parser.getContext(), elts); + return mlir::success(); +} + +LogicalResult ConstStructAttr::verify( + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + mlir::Type type, ArrayAttr members) { + auto sTy = type.dyn_cast_or_null(); + if (!sTy) { + emitError() << "expected !cir.struct type"; + return failure(); + } + + if (sTy.getMembers().size() != members.size()) { + emitError() << "number of elements must match"; + return failure(); + } + + unsigned attrIdx = 0; + for (auto &member : sTy.getMembers()) { + auto m = members[attrIdx].dyn_cast_or_null(); + if (!m) { + emitError() << "expected mlir::TypedAttr attribute"; + return failure(); + } + if (member != m.getType()) { + emitError() << "element at index " << attrIdx << " has type " + << m.getType() << " but return type for this element is " + << member; + return failure(); + } + attrIdx++; + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// LangAttr definitions +//===----------------------------------------------------------------------===// + +Attribute LangAttr::parse(AsmParser &parser, Type odsType) { + auto loc = parser.getCurrentLocation(); + if (parser.parseLess()) + return {}; + + // Parse variable 'lang'. + llvm::StringRef lang; + if (parser.parseKeyword(&lang)) + return {}; + + // Check if parsed value is a valid language. + auto langEnum = symbolizeSourceLanguage(lang); + if (!langEnum.has_value()) { + parser.emitError(loc) << "invalid language keyword '" << lang << "'"; + return {}; + } + + if (parser.parseGreater()) + return {}; + + return get(parser.getContext(), langEnum.value()); +} + +void LangAttr::print(AsmPrinter &printer) const { + printer << "<" << getLang() << '>'; +} + +//===----------------------------------------------------------------------===// +// ConstPtrAttr definitions +//===----------------------------------------------------------------------===// + +Attribute ConstPtrAttr::parse(AsmParser &parser, Type odsType) { + uint64_t value; + + if (!odsType.isa()) + return {}; + + // Consume the '<' symbol. + if (parser.parseLess()) + return {}; + + if (parser.parseKeyword("null").succeeded()) { + value = 0; + } else { + if (parser.parseInteger(value)) + parser.emitError(parser.getCurrentLocation(), "expected integer value"); + } + + // Consume the '>' symbol. + if (parser.parseGreater()) + return {}; + + return ConstPtrAttr::get(odsType, value); +} + +void ConstPtrAttr::print(AsmPrinter &printer) const { + printer << '<'; + if (isNullValue()) + printer << "null"; + else + printer << getValue(); + printer << '>'; +} + +//===----------------------------------------------------------------------===// +// IntAttr definitions +//===----------------------------------------------------------------------===// + +Attribute IntAttr::parse(AsmParser &parser, Type odsType) { + mlir::APInt APValue; + + if (!odsType.isa()) + return {}; + auto type = odsType.cast(); + + // Consume the '<' symbol. + if (parser.parseLess()) + return {}; + + // Fetch arbitrary precision integer value. + if (type.isSigned()) { + int64_t value; + if (parser.parseInteger(value)) + parser.emitError(parser.getCurrentLocation(), "expected integer value"); + APValue = mlir::APInt(type.getWidth(), value, type.isSigned()); + if (APValue.getSExtValue() != value) + parser.emitError(parser.getCurrentLocation(), + "integer value too large for the given type"); + } else { + uint64_t value; + if (parser.parseInteger(value)) + parser.emitError(parser.getCurrentLocation(), "expected integer value"); + APValue = mlir::APInt(type.getWidth(), value, type.isSigned()); + if (APValue.getZExtValue() != value) + parser.emitError(parser.getCurrentLocation(), + "integer value too large for the given type"); + } + + // Consume the '>' symbol. + if (parser.parseGreater()) + return {}; + + return IntAttr::get(type, APValue); +} + +void IntAttr::print(AsmPrinter &printer) const { + auto type = getType().cast(); + printer << '<'; + if (type.isSigned()) + printer << getSInt(); + else + printer << getUInt(); + printer << '>'; +} + +LogicalResult IntAttr::verify(function_ref emitError, + Type type, APInt value) { + if (!type.isa()) { + emitError() << "expected 'simple.int' type"; + return failure(); + } + + auto intType = type.cast(); + if (value.getBitWidth() != intType.getWidth()) { + emitError() << "type and value bitwidth mismatch: " << intType.getWidth() + << " != " << value.getBitWidth(); + return failure(); + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// CIR Dialect +//===----------------------------------------------------------------------===// + +void CIRDialect::registerAttributes() { + addAttributes< +#define GET_ATTRDEF_LIST +#include "clang/CIR/Dialect/IR/CIROpsAttributes.cpp.inc" + >(); +} diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp new file mode 100644 index 000000000000..dd35eae0c391 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -0,0 +1,2461 @@ +//===- CIRDialect.cpp - MLIR CIR ops implementation -----------------------===// +// +// 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 the CIR dialect and its operations. +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIROpsEnums.h" +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include + +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/LLVMIR/LLVMTypes.h" +#include "mlir/IR/Builders.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/Diagnostics.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/IR/DialectInterface.h" +#include "mlir/IR/Location.h" +#include "mlir/IR/OpDefinition.h" +#include "mlir/IR/OpImplementation.h" +#include "mlir/IR/StorageUniquerSupport.h" +#include "mlir/IR/TypeUtilities.h" +#include "mlir/Interfaces/ControlFlowInterfaces.h" +#include "mlir/Interfaces/DataLayoutInterfaces.h" +#include "mlir/IR/FunctionImplementation.h" +#include "mlir/Interfaces/InferTypeOpInterface.h" +#include "mlir/Support/LLVM.h" +#include "mlir/Support/LogicalResult.h" + +using namespace mlir; +using namespace mlir::cir; + +#include "clang/CIR/Dialect/IR/CIROpsEnums.cpp.inc" +#include "clang/CIR/Dialect/IR/CIROpsStructs.cpp.inc" + +#include "clang/CIR/Dialect/IR/CIROpsDialect.cpp.inc" +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" + +//===----------------------------------------------------------------------===// +// CIR Dialect +//===----------------------------------------------------------------------===// +namespace { +struct CIROpAsmDialectInterface : public OpAsmDialectInterface { + using OpAsmDialectInterface::OpAsmDialectInterface; + + AliasResult getAlias(Type type, raw_ostream &os) const final { + if (auto structType = type.dyn_cast()) { + // TODO(cir): generate unique alias names for anonymous records. + if (!structType.getName()) + return AliasResult::NoAlias; + os << "ty_" << structType.getName(); + return AliasResult::OverridableAlias; + } + if (auto intType = type.dyn_cast()) { + os << intType.getAlias(); + return AliasResult::OverridableAlias; + } + if (auto voidType = type.dyn_cast()) { + os << voidType.getAlias(); + return AliasResult::OverridableAlias; + } + + return AliasResult::NoAlias; + } + + AliasResult getAlias(Attribute attr, raw_ostream &os) const final { + if (auto boolAttr = attr.dyn_cast()) { + os << (boolAttr.getValue() ? "true" : "false"); + return AliasResult::FinalAlias; + } + + return AliasResult::NoAlias; + } +}; +} // namespace + +/// Dialect initialization, the instance will be owned by the context. This is +/// the point of registration of types and operations for the dialect. +void cir::CIRDialect::initialize() { + registerTypes(); + registerAttributes(); + addOperations< +#define GET_OP_LIST +#include "clang/CIR/Dialect/IR/CIROps.cpp.inc" + >(); + addInterfaces(); +} + +//===----------------------------------------------------------------------===// +// Helpers +//===----------------------------------------------------------------------===// + +// Parses one of the keywords provided in the list `keywords` and returns the +// position of the parsed keyword in the list. If none of the keywords from the +// list is parsed, returns -1. +static int parseOptionalKeywordAlternative(AsmParser &parser, + ArrayRef keywords) { + for (auto en : llvm::enumerate(keywords)) { + if (succeeded(parser.parseOptionalKeyword(en.value()))) + return en.index(); + } + return -1; +} + +namespace { +template struct EnumTraits {}; + +#define REGISTER_ENUM_TYPE(Ty) \ + template <> struct EnumTraits { \ + static StringRef stringify(Ty value) { return stringify##Ty(value); } \ + static unsigned getMaxEnumVal() { return getMaxEnumValFor##Ty(); } \ + } +#define REGISTER_ENUM_TYPE_WITH_NS(NS, Ty) \ + template <> struct EnumTraits { \ + static StringRef stringify(NS::Ty value) { \ + return NS::stringify##Ty(value); \ + } \ + static unsigned getMaxEnumVal() { return NS::getMaxEnumValFor##Ty(); } \ + } + +REGISTER_ENUM_TYPE(GlobalLinkageKind); +REGISTER_ENUM_TYPE_WITH_NS(sob, SignedOverflowBehavior); +} // namespace + +/// Parse an enum from the keyword, or default to the provided default value. +/// The return type is the enum type by default, unless overriden with the +/// second template argument. +/// TODO: teach other places in this file to use this function. +template +static RetTy parseOptionalCIRKeyword(AsmParser &parser, EnumTy defaultValue) { + SmallVector names; + for (unsigned i = 0, e = EnumTraits::getMaxEnumVal(); i <= e; ++i) + names.push_back(EnumTraits::stringify(static_cast(i))); + + int index = parseOptionalKeywordAlternative(parser, names); + if (index == -1) + return static_cast(defaultValue); + return static_cast(index); +} + +//===----------------------------------------------------------------------===// +// AllocaOp +//===----------------------------------------------------------------------===// + +void 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 +//===----------------------------------------------------------------------===// + +static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType, + mlir::Attribute attrType) { + if (attrType.isa()) { + if (opType.isa<::mlir::cir::PointerType>()) + return success(); + return op->emitOpError("nullptr expects pointer type"); + } + + if (attrType.isa()) { + if (opType.isa<::mlir::cir::StructType, ::mlir::cir::ArrayType>()) + return success(); + return op->emitOpError("zero expects struct or array type"); + } + + if (attrType.isa()) { + if (!opType.isa()) + return op->emitOpError("result type (") + << opType << ") must be '!cir.bool' for '" << attrType << "'"; + return success(); + } + + if (attrType.isa()) { + auto at = attrType.cast(); + if (at.getType() != opType) { + return op->emitOpError("result type (") + << opType << ") does not match value type (" << at.getType() + << ")"; + } + return success(); + } + + if (attrType.isa()) { + if (opType.isa<::mlir::cir::PointerType>()) + return success(); + return op->emitOpError("symbolref expects pointer type"); + } + + if (attrType.isa() || + attrType.isa() || + attrType.isa() || + attrType.isa() || + attrType.isa()) + return success(); + if (attrType.isa()) + return success(); + + assert(attrType.isa() && "What else could we be looking at here?"); + return op->emitOpError("global with type ") + << attrType.cast().getType() << " not supported"; +} + +LogicalResult ConstantOp::verify() { + // ODS already generates checks to make sure the result type is valid. We just + // need to additionally check that the value's attribute type is consistent + // with the result type. + return checkConstantTypes(getOperation(), getType(), getValue()); +} + +static ParseResult parseConstantValue(OpAsmParser &parser, + mlir::Attribute &valueAttr) { + NamedAttrList attr; + if (parser.parseAttribute(valueAttr, "value", attr).failed()) { + return parser.emitError(parser.getCurrentLocation(), + "expected constant attribute to match type"); + } + + return success(); +} + +// FIXME: create a CIRConstAttr and hide this away for both global +// initialization and cir.const operation. +static void printConstant(OpAsmPrinter &p, Attribute value) { + p.printAttribute(value); +} + +static void printConstantValue(OpAsmPrinter &p, cir::ConstantOp op, + Attribute value) { + printConstant(p, value); +} + +OpFoldResult ConstantOp::fold(FoldAdaptor /*adaptor*/) { return getValue(); } + +//===----------------------------------------------------------------------===// +// CastOp +//===----------------------------------------------------------------------===// + +LogicalResult CastOp::verify() { + auto resType = getResult().getType(); + auto srcType = getSrc().getType(); + + switch (getKind()) { + case cir::CastKind::int_to_bool: { + if (!resType.isa()) + return emitOpError() << "requires !cir.bool type for result"; + if (!srcType.isa()) + return emitOpError() << "requires integral type for result"; + return success(); + } + case cir::CastKind::ptr_to_bool: { + if (!resType.isa()) + return emitOpError() << "requires !cir.bool type for result"; + if (!srcType.isa()) + return emitOpError() << "requires pointer type for result"; + return success(); + } + case cir::CastKind::integral: { + if (!resType.isa()) + return emitOpError() << "requires !IntegerType for result"; + if (!srcType.isa()) + return emitOpError() << "requires !IntegerType for source"; + return success(); + } + case cir::CastKind::array_to_ptrdecay: { + auto arrayPtrTy = srcType.dyn_cast(); + auto flatPtrTy = resType.dyn_cast(); + if (!arrayPtrTy || !flatPtrTy) + return emitOpError() << "requires !cir.ptr type for source and result"; + + auto arrayTy = arrayPtrTy.getPointee().dyn_cast(); + if (!arrayTy) + return emitOpError() << "requires !cir.array pointee"; + + if (arrayTy.getEltType() != flatPtrTy.getPointee()) + return emitOpError() + << "requires same type for array element and pointee result"; + return success(); + } + case cir::CastKind::bitcast: { + if (!srcType.dyn_cast() || + !resType.dyn_cast()) + return emitOpError() << "requires !cir.ptr type for source and result"; + return success(); + } + case cir::CastKind::floating: { + if (!srcType.dyn_cast() || + !resType.dyn_cast()) + return emitOpError() << "requries floating for source and result"; + return success(); + } + case cir::CastKind::float_to_int: { + if (!srcType.dyn_cast()) + return emitOpError() << "requires floating for source"; + if (!resType.dyn_cast()) + return emitOpError() << "requires !IntegerType for result"; + return success(); + } + case cir::CastKind::int_to_ptr: { + if (!srcType.dyn_cast()) + return emitOpError() << "requires integer for source"; + if (!resType.dyn_cast()) + return emitOpError() << "requires pointer for result"; + return success(); + } + case cir::CastKind::ptr_to_int: { + if (!srcType.dyn_cast()) + return emitOpError() << "requires pointer for source"; + if (!resType.dyn_cast()) + return emitOpError() << "requires integer for result"; + return success(); + } + case cir::CastKind::float_to_bool: { + if (!srcType.isa()) + return emitOpError() << "requires float for source"; + if (!resType.isa()) + return emitOpError() << "requires !cir.bool for result"; + return success(); + } + case cir::CastKind::bool_to_int: { + if (!srcType.isa()) + return emitOpError() << "requires !cir.bool for source"; + if (!resType.isa()) + return emitOpError() << "requires !cir.int for result"; + return success(); + } + case cir::CastKind::int_to_float: + if (!srcType.isa()) + return emitOpError() << "requires !cir.int for source"; + if (!resType.isa()) + return emitOpError() << "requires !cir.float for result"; + return success(); + } + + llvm_unreachable("Unknown CastOp kind?"); +} + +//===----------------------------------------------------------------------===// +// ReturnOp +//===----------------------------------------------------------------------===// + +static mlir::LogicalResult checkReturnAndFunction(ReturnOp op, + cir::FuncOp function) { + // ReturnOps currently only have a single optional operand. + if (op.getNumOperands() > 1) + return op.emitOpError() << "expects at most 1 return operand"; + + // Ensure returned type matches the function signature. + auto expectedTy = function.getFunctionType().getReturnType(); + auto actualTy = + (op.getNumOperands() == 0 ? mlir::cir::VoidType::get(op.getContext()) + : op.getOperand(0).getType()); + if (actualTy != expectedTy) + return op.emitOpError() << "returns " << actualTy + << " but enclosing function returns " << expectedTy; + + return mlir::success(); +} + +mlir::LogicalResult ReturnOp::verify() { + // Returns can be present in multiple different scopes, get the + // wrapping function and start from there. + auto *fnOp = getOperation()->getParentOp(); + while (!isa(fnOp)) + fnOp = fnOp->getParentOp(); + + // Make sure return types match function return type. + if (checkReturnAndFunction(*this, cast(fnOp)).failed()) + return failure(); + + return success(); +} + +//===----------------------------------------------------------------------===// +// ThrowOp +//===----------------------------------------------------------------------===// + +mlir::LogicalResult ThrowOp::verify() { + // For the no-rethrow version, it must have at least the exception pointer. + if (rethrows()) + return success(); + + if (getNumOperands() == 1) { + if (!getTypeInfo()) + return emitOpError() << "'type_info' symbol attribute missing"; + return success(); + } + + return failure(); +} + +//===----------------------------------------------------------------------===// +// IfOp +//===----------------------------------------------------------------------===// + +static LogicalResult checkBlockTerminator(OpAsmParser &parser, + llvm::SMLoc parserLoc, + std::optional l, Region *r, + bool ensureTerm = true) { + mlir::Builder &builder = parser.getBuilder(); + if (r->hasOneBlock()) { + if (ensureTerm) { + ::mlir::impl::ensureRegionTerminator( + *r, builder, *l, [](OpBuilder &builder, Location loc) { + OperationState state(loc, YieldOp::getOperationName()); + YieldOp::build(builder, state); + return Operation::create(state); + }); + } else { + assert(r && "region must not be empty"); + Block &block = r->back(); + if (block.empty() || !block.back().hasTrait()) { + return parser.emitError( + parser.getCurrentLocation(), + "blocks are expected to be explicitly terminated"); + } + } + return success(); + } + + // Empty regions don't need any handling. + auto &blocks = r->getBlocks(); + if (blocks.empty()) + return success(); + + // Test that at least one block has a yield/return/throw terminator. We can + // probably make this a bit more strict. + for (Block &block : blocks) { + if (block.empty()) + continue; + auto &op = block.back(); + if (op.hasTrait() && + isa(op)) { + return success(); + } + } + + parser.emitError(parserLoc, + "expected at least one block with cir.yield or cir.return"); + return failure(); +} + +ParseResult cir::IfOp::parse(OpAsmParser &parser, OperationState &result) { + // Create the regions for 'then'. + result.regions.reserve(2); + Region *thenRegion = result.addRegion(); + Region *elseRegion = result.addRegion(); + + auto &builder = parser.getBuilder(); + OpAsmParser::UnresolvedOperand cond; + Type boolType = ::mlir::cir::BoolType::get(builder.getContext()); + + if (parser.parseOperand(cond) || + parser.resolveOperand(cond, boolType, result.operands)) + return failure(); + + // Parse the 'then' region. + auto parseThenLoc = parser.getCurrentLocation(); + if (parser.parseRegion(*thenRegion, /*arguments=*/{}, + /*argTypes=*/{})) + return failure(); + if (checkBlockTerminator(parser, parseThenLoc, result.location, thenRegion) + .failed()) + return failure(); + + // If we find an 'else' keyword, parse the 'else' region. + if (!parser.parseOptionalKeyword("else")) { + auto parseElseLoc = parser.getCurrentLocation(); + if (parser.parseRegion(*elseRegion, /*arguments=*/{}, /*argTypes=*/{})) + return failure(); + if (checkBlockTerminator(parser, parseElseLoc, result.location, elseRegion) + .failed()) + return failure(); + } + + // Parse the optional attribute list. + if (parser.parseOptionalAttrDict(result.attributes)) + return failure(); + return success(); +} + +bool shouldPrintTerm(mlir::Region &r) { + if (!r.hasOneBlock()) + return true; + auto *entryBlock = &r.front(); + if (entryBlock->empty()) + return false; + if (isa(entryBlock->back())) + return true; + if (isa(entryBlock->back())) + return true; + YieldOp y = dyn_cast(entryBlock->back()); + if (y && (!y.isPlain() || !y.getArgs().empty())) + return true; + return false; +} + +void cir::IfOp::print(OpAsmPrinter &p) { + p << " " << getCondition() << " "; + auto &thenRegion = this->getThenRegion(); + p.printRegion(thenRegion, + /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/shouldPrintTerm(thenRegion)); + + // Print the 'else' regions if it exists and has a block. + auto &elseRegion = this->getElseRegion(); + if (!elseRegion.empty()) { + p << " else "; + p.printRegion(elseRegion, + /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/shouldPrintTerm(elseRegion)); + } + + p.printOptionalAttrDict(getOperation()->getAttrs()); +} + +/// Default callback for IfOp builders. Inserts nothing for now. +void mlir::cir::buildTerminatedBody(OpBuilder &builder, Location loc) {} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes that +/// correspond to a constant value for each operand, or null if that operand is +/// not a constant. +void IfOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // The `then` and the `else` region branch back to the parent operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // Don't consider the else region if it is empty. + Region *elseRegion = &this->getElseRegion(); + if (elseRegion->empty()) + elseRegion = nullptr; + + // Otherwise, the successor is dependent on the condition. + // bool condition; + // if (auto condAttr= operands.front().dyn_cast_or_null()) { + // assert(0 && "not implemented"); + // // condition = condAttr.getValue().isOneValue(); + // // Add the successor regions using the condition. + // // regions.push_back(RegionSuccessor(condition ? &thenRegion() : + // // elseRegion)); + // // return; + // } + + // If the condition isn't constant, both regions may be executed. + regions.push_back(RegionSuccessor(&getThenRegion())); + // If the else region does not exist, it is not a viable successor. + if (elseRegion) + regions.push_back(RegionSuccessor(elseRegion)); + return; +} + +void IfOp::build(OpBuilder &builder, OperationState &result, Value cond, + bool withElseRegion, + function_ref thenBuilder, + function_ref elseBuilder) { + assert(thenBuilder && "the builder callback for 'then' must be present"); + + result.addOperands(cond); + + OpBuilder::InsertionGuard guard(builder); + Region *thenRegion = result.addRegion(); + builder.createBlock(thenRegion); + thenBuilder(builder, result.location); + + Region *elseRegion = result.addRegion(); + if (!withElseRegion) + return; + + builder.createBlock(elseRegion); + elseBuilder(builder, result.location); +} + +LogicalResult IfOp::verify() { return success(); } + +//===----------------------------------------------------------------------===// +// ScopeOp +//===----------------------------------------------------------------------===// + +ParseResult cir::ScopeOp::parse(OpAsmParser &parser, OperationState &result) { + // Create one region within 'scope'. + result.regions.reserve(1); + Region *scopeRegion = result.addRegion(); + auto loc = parser.getCurrentLocation(); + + // Parse the scope region. + if (parser.parseRegion(*scopeRegion, /*arguments=*/{}, /*argTypes=*/{})) + return failure(); + + if (checkBlockTerminator(parser, loc, result.location, scopeRegion).failed()) + return failure(); + + // Parse the optional attribute list. + if (parser.parseOptionalAttrDict(result.attributes)) + return failure(); + return success(); +} + +void cir::ScopeOp::print(OpAsmPrinter &p) { + p << ' '; + auto &scopeRegion = this->getScopeRegion(); + p.printRegion(scopeRegion, + /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/shouldPrintTerm(scopeRegion)); + + p.printOptionalAttrDict(getOperation()->getAttrs()); +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes that +/// correspond to a constant value for each operand, or null if that operand is +/// not a constant. +void ScopeOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // The only region always branch back to the parent operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor(getResults())); + return; + } + + // If the condition isn't constant, both regions may be executed. + regions.push_back(RegionSuccessor(&getScopeRegion())); +} + +void ScopeOp::build( + OpBuilder &builder, OperationState &result, + function_ref scopeBuilder) { + assert(scopeBuilder && "the builder callback for 'then' must be present"); + + OpBuilder::InsertionGuard guard(builder); + Region *scopeRegion = result.addRegion(); + builder.createBlock(scopeRegion); + + mlir::Type yieldTy; + scopeBuilder(builder, yieldTy, result.location); + + if (yieldTy) + result.addTypes(TypeRange{yieldTy}); +} + +void ScopeOp::build(OpBuilder &builder, OperationState &result, + function_ref scopeBuilder) { + assert(scopeBuilder && "the builder callback for 'then' must be present"); + OpBuilder::InsertionGuard guard(builder); + Region *scopeRegion = result.addRegion(); + builder.createBlock(scopeRegion); + scopeBuilder(builder, result.location); +} + +LogicalResult ScopeOp::verify() { return success(); } + +//===----------------------------------------------------------------------===// +// TryOp +//===----------------------------------------------------------------------===// + +void TryOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // The only region always branch back to the parent operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor(this->getODSResults(0))); + return; + } + + // If the condition isn't constant, both regions may be executed. + regions.push_back(RegionSuccessor(&getBody())); +} + +//===----------------------------------------------------------------------===// +// TernaryOp +//===----------------------------------------------------------------------===// + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes that +/// correspond to a constant value for each operand, or null if that operand is +/// not a constant. +void TernaryOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // The `true` and the `false` region branch back to the parent operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor(this->getODSResults(0))); + return; + } + + // Try optimize if we have more information + // if (auto condAttr = operands.front().dyn_cast_or_null()) { + // assert(0 && "not implemented"); + // } + + // If the condition isn't constant, both regions may be executed. + regions.push_back(RegionSuccessor(&getTrueRegion())); + regions.push_back(RegionSuccessor(&getFalseRegion())); + return; +} + +void TernaryOp::build(OpBuilder &builder, OperationState &result, Value cond, + function_ref trueBuilder, + function_ref falseBuilder) { + result.addOperands(cond); + OpBuilder::InsertionGuard guard(builder); + Region *trueRegion = result.addRegion(); + auto *block = builder.createBlock(trueRegion); + trueBuilder(builder, result.location); + Region *falseRegion = result.addRegion(); + builder.createBlock(falseRegion); + falseBuilder(builder, result.location); + + auto yield = dyn_cast(block->getTerminator()); + assert((yield && yield.getNumOperands() <= 1) && + "expected zero or one result type"); + if (yield.getNumOperands() == 1) + result.addTypes(TypeRange{yield.getOperandTypes().front()}); +} + +//===----------------------------------------------------------------------===// +// YieldOp +//===----------------------------------------------------------------------===// + +mlir::LogicalResult YieldOp::verify() { + auto isDominatedByLoopOrSwitch = [&](Operation *parentOp) { + while (!llvm::isa(parentOp)) { + if (llvm::isa(parentOp)) + return true; + parentOp = parentOp->getParentOp(); + } + + emitOpError() << "shall be dominated by 'cir.loop' or 'cir.switch'"; + return false; + }; + + auto isDominatedByProperAwaitRegion = [&](Operation *parentOp, + mlir::Region *currRegion) { + while (!llvm::isa(parentOp)) { + auto awaitOp = dyn_cast(parentOp); + if (awaitOp) { + if (currRegion && currRegion == &awaitOp.getResume()) { + emitOpError() << "kind 'nosuspend' can only be used in 'ready' and " + "'suspend' regions"; + return false; + } + return true; + } + + currRegion = parentOp->getParentRegion(); + parentOp = parentOp->getParentOp(); + } + + emitOpError() << "shall be dominated by 'cir.await'"; + return false; + }; + + auto isDominatedByLoop = [](Operation *parentOp) { + while (!llvm::isa(parentOp)) { + if (llvm::isa(parentOp)) + return true; + parentOp = parentOp->getParentOp(); + } + return false; + }; + + if (isNoSuspend()) { + if (!isDominatedByProperAwaitRegion(getOperation()->getParentOp(), + getOperation()->getParentRegion())) + return mlir::failure(); + return mlir::success(); + } + + if (isBreak()) { + if (!isDominatedByLoopOrSwitch(getOperation()->getParentOp())) + return mlir::failure(); + return mlir::success(); + } + + if (isContinue()) { + if (!isDominatedByLoop(getOperation()->getParentOp())) + return emitOpError() << "shall be dominated by 'cir.loop'"; + return mlir::success(); + } + + if (isFallthrough()) { + if (!llvm::isa(getOperation()->getParentOp())) + return emitOpError() << "fallthrough only expected within 'cir.switch'"; + return mlir::success(); + } + + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// BrOp +//===----------------------------------------------------------------------===// + +mlir::SuccessorOperands BrOp::getSuccessorOperands(unsigned index) { + assert(index == 0 && "invalid successor index"); + return mlir::SuccessorOperands(getDestOperandsMutable()); +} + +Block *BrOp::getSuccessorForOperands(ArrayRef) { return getDest(); } + +//===----------------------------------------------------------------------===// +// BrCondOp +//===----------------------------------------------------------------------===// + +mlir::SuccessorOperands BrCondOp::getSuccessorOperands(unsigned index) { + assert(index < getNumSuccessors() && "invalid successor index"); + return SuccessorOperands(index == 0 ? getDestOperandsTrueMutable() + : getDestOperandsFalseMutable()); +} + +Block *BrCondOp::getSuccessorForOperands(ArrayRef operands) { + if (IntegerAttr condAttr = operands.front().dyn_cast_or_null()) + return condAttr.getValue().isOne() ? getDestTrue() : getDestFalse(); + return nullptr; +} + +//===----------------------------------------------------------------------===// +// SwitchOp +//===----------------------------------------------------------------------===// + +ParseResult +parseSwitchOp(OpAsmParser &parser, + llvm::SmallVectorImpl> ®ions, + ::mlir::ArrayAttr &casesAttr, + mlir::OpAsmParser::UnresolvedOperand &cond, + mlir::Type &condType) { + mlir::cir::IntType intCondType; + SmallVector cases; + + auto parseAndCheckRegion = [&]() -> ParseResult { + // Parse region attached to case + regions.emplace_back(new Region); + Region &currRegion = *regions.back().get(); + auto parserLoc = parser.getCurrentLocation(); + if (parser.parseRegion(currRegion, /*arguments=*/{}, /*argTypes=*/{})) { + regions.clear(); + return failure(); + } + + if (currRegion.empty()) { + return parser.emitError(parser.getCurrentLocation(), + "case region shall not be empty"); + } + + if (checkBlockTerminator(parser, parserLoc, std::nullopt, &currRegion, + /*ensureTerm=*/false) + .failed()) + return failure(); + return success(); + }; + + auto parseCase = [&]() -> ParseResult { + auto loc = parser.getCurrentLocation(); + if (parser.parseKeyword("case").failed()) + return parser.emitError(loc, "expected 'case' keyword here"); + + if (parser.parseLParen().failed()) + return parser.emitError(parser.getCurrentLocation(), "expected '('"); + + ::llvm::StringRef attrStr; + ::mlir::NamedAttrList attrStorage; + + // case (equal, 20) { + // ... + // 1. Get the case kind + // 2. Get the value (next in list) + + // These needs to be in sync with CIROps.td + if (parser.parseOptionalKeyword(&attrStr, {"default", "equal", "anyof"})) { + ::mlir::StringAttr attrVal; + ::mlir::OptionalParseResult parseResult = parser.parseOptionalAttribute( + attrVal, parser.getBuilder().getNoneType(), "kind", attrStorage); + if (parseResult.has_value()) { + if (failed(*parseResult)) + return ::mlir::failure(); + attrStr = attrVal.getValue(); + } + } + + if (attrStr.empty()) { + return parser.emitError( + loc, "expected string or keyword containing one of the following " + "enum values for attribute 'kind' [default, equal, anyof]"); + } + + auto attrOptional = ::mlir::cir::symbolizeCaseOpKind(attrStr.str()); + if (!attrOptional) + return parser.emitError(loc, "invalid ") + << "kind attribute specification: \"" << attrStr << '"'; + + auto kindAttr = ::mlir::cir::CaseOpKindAttr::get( + parser.getBuilder().getContext(), attrOptional.value()); + + // `,` value or `,` [values,...] + SmallVector caseEltValueListAttr; + mlir::ArrayAttr caseValueList; + + switch (kindAttr.getValue()) { + case cir::CaseOpKind::Equal: { + if (parser.parseComma().failed()) + return mlir::failure(); + int64_t val = 0; + if (parser.parseInteger(val).failed()) + return ::mlir::failure(); + caseEltValueListAttr.push_back(mlir::cir::IntAttr::get(intCondType, val)); + break; + } + case cir::CaseOpKind::Anyof: { + if (parser.parseComma().failed()) + return mlir::failure(); + if (parser.parseLSquare().failed()) + return mlir::failure(); + if (parser.parseCommaSeparatedList([&]() { + int64_t val = 0; + if (parser.parseInteger(val).failed()) + return ::mlir::failure(); + caseEltValueListAttr.push_back( + mlir::cir::IntAttr::get(intCondType, val)); + return ::mlir::success(); + })) + return mlir::failure(); + if (parser.parseRSquare().failed()) + return mlir::failure(); + break; + } + case cir::CaseOpKind::Default: { + if (parser.parseRParen().failed()) + return parser.emitError(parser.getCurrentLocation(), "expected ')'"); + cases.push_back(cir::CaseAttr::get( + parser.getContext(), parser.getBuilder().getArrayAttr({}), kindAttr)); + return parseAndCheckRegion(); + } + } + + caseValueList = parser.getBuilder().getArrayAttr(caseEltValueListAttr); + cases.push_back( + cir::CaseAttr::get(parser.getContext(), caseValueList, kindAttr)); + if (succeeded(parser.parseOptionalColon())) { + Type caseIntTy; + if (parser.parseType(caseIntTy).failed()) + return parser.emitError(parser.getCurrentLocation(), "expected type"); + if (intCondType != caseIntTy) + return parser.emitError(parser.getCurrentLocation(), + "expected a match with the condition type"); + } + if (parser.parseRParen().failed()) + return parser.emitError(parser.getCurrentLocation(), "expected ')'"); + return parseAndCheckRegion(); + }; + + if (parser.parseLParen()) + return ::mlir::failure(); + + if (parser.parseOperand(cond)) + return ::mlir::failure(); + if (parser.parseColon()) + return ::mlir::failure(); + if (parser.parseCustomTypeWithFallback(intCondType)) + return ::mlir::failure(); + condType = intCondType; + if (parser.parseRParen()) + return ::mlir::failure(); + + if (parser + .parseCommaSeparatedList(OpAsmParser::Delimiter::Square, parseCase, + " in cases list") + .failed()) + return failure(); + + casesAttr = parser.getBuilder().getArrayAttr(cases); + return ::mlir::success(); +} + +void printSwitchOp(OpAsmPrinter &p, SwitchOp op, + mlir::MutableArrayRef<::mlir::Region> regions, + mlir::ArrayAttr casesAttr, mlir::Value condition, + mlir::Type condType) { + int idx = 0, lastIdx = regions.size() - 1; + + p << "("; + p << condition; + p << " : "; + p.printStrippedAttrOrType(condType); + p << ") ["; + // FIXME: ideally we want some extra indentation for "cases" but too + // cumbersome to pull it out now, since most handling is private. Perhaps + // better improve overall mechanism. + p.printNewline(); + for (auto &r : regions) { + p << "case ("; + + auto attr = casesAttr[idx].cast(); + auto kind = attr.getKind().getValue(); + assert((kind == CaseOpKind::Default || kind == CaseOpKind::Equal || + kind == CaseOpKind::Anyof) && + "unknown case"); + + // Case kind + p << stringifyCaseOpKind(kind); + + // Case value + switch (kind) { + case cir::CaseOpKind::Equal: { + p << ", "; + auto intAttr = attr.getValue()[0].cast(); + auto intAttrTy = intAttr.getType().cast(); + (intAttrTy.isSigned() ? p << intAttr.getSInt() : p << intAttr.getUInt()); + break; + } + case cir::CaseOpKind::Anyof: { + p << ", ["; + llvm::interleaveComma(attr.getValue(), p, [&](const Attribute &a) { + auto intAttr = a.cast(); + auto intAttrTy = intAttr.getType().cast(); + (intAttrTy.isSigned() ? p << intAttr.getSInt() + : p << intAttr.getUInt()); + }); + p << "] : "; + auto typedAttr = attr.getValue()[0].dyn_cast(); + assert(typedAttr && "this should never not have a type!"); + p.printType(typedAttr.getType()); + break; + } + case cir::CaseOpKind::Default: + break; + } + + p << ") "; + p.printRegion(r, /*printEntryBLockArgs=*/false, + /*printBlockTerminators=*/true); + if (idx < lastIdx) + p << ","; + p.printNewline(); + idx++; + } + p << "]"; +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes +/// that correspond to a constant value for each operand, or null if that +/// operand is not a constant. +void SwitchOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // If any index all the underlying regions branch back to the parent + // operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // for (auto &r : this->getRegions()) { + // // If we can figure out the case stmt we are landing, this can be + // // overly simplified. + // // bool condition; + // if (auto condAttr = operands.front().dyn_cast_or_null()) { + // assert(0 && "not implemented"); + // (void)r; + // // condition = condAttr.getValue().isOneValue(); + // // Add the successor regions using the condition. + // // regions.push_back(RegionSuccessor(condition ? &thenRegion() : + // // elseRegion)); + // // return; + // } + // } + + // If the condition isn't constant, all regions may be executed. + for (auto &r : this->getRegions()) + regions.push_back(RegionSuccessor(&r)); +} + +LogicalResult SwitchOp::verify() { + if (getCases().has_value() && getCases()->size() != getNumRegions()) + return emitOpError("number of cases attributes and regions must match"); + return success(); +} + +void SwitchOp::build( + OpBuilder &builder, OperationState &result, Value cond, + function_ref switchBuilder) { + assert(switchBuilder && "the builder callback for regions must be present"); + OpBuilder::InsertionGuard guardSwitch(builder); + result.addOperands({cond}); + switchBuilder(builder, result.location, result); +} + +//===----------------------------------------------------------------------===// +// CatchOp +//===----------------------------------------------------------------------===// + +ParseResult +parseCatchOp(OpAsmParser &parser, + llvm::SmallVectorImpl> ®ions, + ::mlir::ArrayAttr &catchersAttr) { + SmallVector catchList; + + auto parseAndCheckRegion = [&]() -> ParseResult { + // Parse region attached to catch + regions.emplace_back(new Region); + Region &currRegion = *regions.back().get(); + auto parserLoc = parser.getCurrentLocation(); + if (parser.parseRegion(currRegion, /*arguments=*/{}, /*argTypes=*/{})) { + regions.clear(); + return failure(); + } + + if (currRegion.empty()) { + return parser.emitError(parser.getCurrentLocation(), + "catch region shall not be empty"); + } + + if (checkBlockTerminator(parser, parserLoc, std::nullopt, &currRegion, + /*ensureTerm=*/false) + .failed()) + return failure(); + return success(); + }; + + auto parseCatchEntry = [&]() -> ParseResult { + mlir::Type exceptionType; + mlir::Attribute exceptionTypeInfo; + + // cir.catch(..., [ + // type (!cir.ptr, @type_info_char_star) { + // ... + // }, + // all { + // ... + // } + // ] + ::llvm::StringRef attrStr; + if (!parser.parseOptionalKeyword(&attrStr, {"all"})) { + if (parser.parseKeyword("type").failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected 'type' keyword here"); + + if (parser.parseLParen().failed()) + return parser.emitError(parser.getCurrentLocation(), "expected '('"); + + if (parser.parseType(exceptionType).failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected valid exception type"); + if (parser.parseAttribute(exceptionTypeInfo).failed()) + return parser.emitError(parser.getCurrentLocation(), + "expected valid RTTI info attribute"); + if (parser.parseRParen().failed()) + return parser.emitError(parser.getCurrentLocation(), "expected ')'"); + } + catchList.push_back(mlir::cir::CatchEntryAttr::get( + parser.getContext(), exceptionType, exceptionTypeInfo)); + + return parseAndCheckRegion(); + }; + + if (parser + .parseCommaSeparatedList(OpAsmParser::Delimiter::Square, + parseCatchEntry, " in catch list") + .failed()) + return failure(); + + catchersAttr = parser.getBuilder().getArrayAttr(catchList); + return ::mlir::success(); +} + +void printCatchOp(OpAsmPrinter &p, CatchOp op, + mlir::MutableArrayRef<::mlir::Region> regions, + mlir::ArrayAttr catchList) { + + int currCatchIdx = 0; + p << "["; + llvm::interleaveComma(catchList, p, [&](const Attribute &a) { + p.printNewline(); + p.increaseIndent(); + auto attr = a.cast(); + auto exType = attr.getExceptionType(); + auto exRtti = attr.getExceptionTypeInfo(); + + if (!exType) { + p << "all"; + } else { + p << "type ("; + p.printType(exType); + p << ", "; + p.printAttribute(exRtti); + p << ") "; + } + p.printNewline(); + p.increaseIndent(); + p.printRegion(regions[currCatchIdx], /*printEntryBLockArgs=*/false, + /*printBlockTerminators=*/true); + currCatchIdx++; + p.decreaseIndent(); + p.decreaseIndent(); + }); + p << "]"; +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes +/// that correspond to a constant value for each operand, or null if that +/// operand is not a constant. +void CatchOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // If any index all the underlying regions branch back to the parent + // operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // FIXME: optimize, ideas include: + // - If we know a target function never throws a specific type, we can + // remove the catch handler. + // - ??? + + // If the condition isn't constant, all regions may be executed. + for (auto &r : this->getRegions()) + regions.push_back(RegionSuccessor(&r)); +} + +void CatchOp::build( + OpBuilder &builder, OperationState &result, + function_ref catchBuilder) { + assert(catchBuilder && "the builder callback for regions must be present"); + OpBuilder::InsertionGuard guardCatch(builder); + catchBuilder(builder, result.location, result); +} + +//===----------------------------------------------------------------------===// +// LoopOp +//===----------------------------------------------------------------------===// + +void LoopOp::build(OpBuilder &builder, OperationState &result, + cir::LoopOpKind kind, + function_ref condBuilder, + function_ref bodyBuilder, + function_ref stepBuilder) { + OpBuilder::InsertionGuard guard(builder); + ::mlir::cir::LoopOpKindAttr kindAttr = + cir::LoopOpKindAttr::get(builder.getContext(), kind); + result.addAttribute(getKindAttrName(result.name), kindAttr); + + Region *condRegion = result.addRegion(); + builder.createBlock(condRegion); + condBuilder(builder, result.location); + + Region *bodyRegion = result.addRegion(); + builder.createBlock(bodyRegion); + bodyBuilder(builder, result.location); + + Region *stepRegion = result.addRegion(); + builder.createBlock(stepRegion); + stepBuilder(builder, result.location); +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes +/// that correspond to a constant value for each operand, or null if that +/// operand is not a constant. +void LoopOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // If any index all the underlying regions branch back to the parent + // operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // FIXME: we want to look at cond region for getting more accurate results + // if the other regions will get a chance to execute. + regions.push_back(RegionSuccessor(&this->getCond())); + regions.push_back(RegionSuccessor(&this->getBody())); + regions.push_back(RegionSuccessor(&this->getStep())); +} + +llvm::SmallVector LoopOp::getLoopRegions() { return {&getBody()}; } + +LogicalResult LoopOp::verify() { + // Cond regions should only terminate with plain 'cir.yield' or + // 'cir.yield continue'. + auto terminateError = [&]() { + return emitOpError() << "cond region must be terminated with " + "'cir.yield' or 'cir.yield continue'"; + }; + + auto &blocks = getCond().getBlocks(); + for (Block &block : blocks) { + if (block.empty()) + continue; + auto &op = block.back(); + if (isa(op)) + continue; + if (!isa(op)) + terminateError(); + auto y = cast(op); + if (!(y.isPlain() || y.isContinue())) + terminateError(); + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// GlobalOp +//===----------------------------------------------------------------------===// + +static void printGlobalOpTypeAndInitialValue(OpAsmPrinter &p, GlobalOp op, + TypeAttr type, Attribute initAttr, + mlir::Region &ctorRegion, + mlir::Region &dtorRegion) { + auto printType = [&]() { p << ": " << type; }; + if (!op.isDeclaration()) { + p << "= "; + if (!ctorRegion.empty()) { + p << "ctor "; + printType(); + p << " "; + p.printRegion(ctorRegion, + /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/false); + } else { + // This also prints the type... + if (initAttr) + printConstant(p, initAttr); + } + + if (!dtorRegion.empty()) { + p << " dtor "; + p.printRegion(dtorRegion, + /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/false); + } + } else { + printType(); + } +} + +static ParseResult parseGlobalOpTypeAndInitialValue(OpAsmParser &parser, + TypeAttr &typeAttr, + Attribute &initialValueAttr, + mlir::Region &ctorRegion, + mlir::Region &dtorRegion) { + mlir::Type opTy; + if (parser.parseOptionalEqual().failed()) { + // Absence of equal means a declaration, so we need to parse the type. + // cir.global @a : i32 + if (parser.parseColonType(opTy)) + return failure(); + } else { + // Parse contructor, example: + // cir.global @rgb = ctor : type { ... } + if (!parser.parseOptionalKeyword("ctor")) { + if (parser.parseColonType(opTy)) + return failure(); + auto parseLoc = parser.getCurrentLocation(); + if (parser.parseRegion(ctorRegion, /*arguments=*/{}, /*argTypes=*/{})) + return failure(); + if (!ctorRegion.hasOneBlock()) + return parser.emitError(parser.getCurrentLocation(), + "ctor region must have exactly one block"); + if (ctorRegion.back().empty()) + return parser.emitError(parser.getCurrentLocation(), + "ctor region shall not be empty"); + if (checkBlockTerminator(parser, parseLoc, + ctorRegion.back().back().getLoc(), &ctorRegion) + .failed()) + return failure(); + } else { + // Parse constant with initializer, examples: + // cir.global @y = 3.400000e+00 : f32 + // cir.global @rgb = #cir.const_array<[...] : !cir.array> + if (parseConstantValue(parser, initialValueAttr).failed()) + return failure(); + + assert(initialValueAttr.isa() && + "Non-typed attrs shouldn't appear here."); + auto typedAttr = initialValueAttr.cast(); + opTy = typedAttr.getType(); + } + + // Parse destructor, example: + // dtor { ... } + if (!parser.parseOptionalKeyword("dtor")) { + auto parseLoc = parser.getCurrentLocation(); + if (parser.parseRegion(dtorRegion, /*arguments=*/{}, /*argTypes=*/{})) + return failure(); + if (!dtorRegion.hasOneBlock()) + return parser.emitError(parser.getCurrentLocation(), + "dtor region must have exactly one block"); + if (dtorRegion.back().empty()) + return parser.emitError(parser.getCurrentLocation(), + "dtor region shall not be empty"); + if (checkBlockTerminator(parser, parseLoc, + dtorRegion.back().back().getLoc(), &dtorRegion) + .failed()) + return failure(); + } + } + + typeAttr = TypeAttr::get(opTy); + return success(); +} + +LogicalResult GlobalOp::verify() { + // Verify that the initial value, if present, is either a unit attribute or + // an attribute CIR supports. + if (getInitialValue().has_value()) { + if (checkConstantTypes(getOperation(), getSymType(), *getInitialValue()) + .failed()) + return failure(); + } + + // Verify that the constructor region, if present, has only one block which is + // not empty. + auto &ctorRegion = getCtorRegion(); + if (!ctorRegion.empty()) { + if (!ctorRegion.hasOneBlock()) { + return emitError() << "ctor region must have exactly one block."; + } + + auto &block = ctorRegion.front(); + if (block.empty()) { + return emitError() << "ctor region shall not be empty."; + } + } + + // Verify that the destructor region, if present, has only one block which is + // not empty. + auto &dtorRegion = getDtorRegion(); + if (!dtorRegion.empty()) { + if (!dtorRegion.hasOneBlock()) { + return emitError() << "dtor region must have exactly one block."; + } + + auto &block = dtorRegion.front(); + if (block.empty()) { + return emitError() << "dtor region shall not be empty."; + } + } + + if (std::optional alignAttr = getAlignment()) { + uint64_t alignment = alignAttr.value(); + if (!llvm::isPowerOf2_64(alignment)) + return emitError() << "alignment attribute value " << alignment + << " is not a power of 2"; + } + + switch (getLinkage()) { + case GlobalLinkageKind::InternalLinkage: + case GlobalLinkageKind::PrivateLinkage: + if (isPublic()) + return emitError() << "public visibility not allowed with '" + << stringifyGlobalLinkageKind(getLinkage()) + << "' linkage"; + break; + case GlobalLinkageKind::ExternalLinkage: + case GlobalLinkageKind::ExternalWeakLinkage: + case GlobalLinkageKind::LinkOnceODRLinkage: + case GlobalLinkageKind::LinkOnceAnyLinkage: + // FIXME: mlir's concept of visibility gets tricky with LLVM ones, + // for instance, symbol declarations cannot be "public", so we + // have to mark them "private" to workaround the symbol verifier. + if (isPrivate() && !isDeclaration()) + return emitError() << "private visibility not allowed with '" + << stringifyGlobalLinkageKind(getLinkage()) + << "' linkage"; + break; + default: + emitError() << stringifyGlobalLinkageKind(getLinkage()) + << ": verifier not implemented\n"; + return failure(); + } + + // TODO: verify visibility for declarations? + return success(); +} + +void GlobalOp::build(OpBuilder &odsBuilder, OperationState &odsState, + StringRef sym_name, Type sym_type, bool isConstant, + cir::GlobalLinkageKind linkage, + function_ref ctorBuilder, + function_ref dtorBuilder) { + odsState.addAttribute(getSymNameAttrName(odsState.name), + odsBuilder.getStringAttr(sym_name)); + odsState.addAttribute(getSymTypeAttrName(odsState.name), + ::mlir::TypeAttr::get(sym_type)); + if (isConstant) + odsState.addAttribute(getConstantAttrName(odsState.name), + odsBuilder.getUnitAttr()); + + ::mlir::cir::GlobalLinkageKindAttr linkageAttr = + cir::GlobalLinkageKindAttr::get(odsBuilder.getContext(), linkage); + odsState.addAttribute(getLinkageAttrName(odsState.name), linkageAttr); + + Region *ctorRegion = odsState.addRegion(); + if (ctorBuilder) { + odsBuilder.createBlock(ctorRegion); + ctorBuilder(odsBuilder, odsState.location); + } + + Region *dtorRegion = odsState.addRegion(); + if (dtorBuilder) { + odsBuilder.createBlock(dtorRegion); + dtorBuilder(odsBuilder, odsState.location); + } +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes that +/// correspond to a constant value for each operand, or null if that operand is +/// not a constant. +void GlobalOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // The `ctor` and `dtor` regions always branch back to the parent operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // Don't consider the ctor region if it is empty. + Region *ctorRegion = &this->getCtorRegion(); + if (ctorRegion->empty()) + ctorRegion = nullptr; + + // Don't consider the dtor region if it is empty. + Region *dtorRegion = &this->getCtorRegion(); + if (dtorRegion->empty()) + dtorRegion = nullptr; + + // If the condition isn't constant, both regions may be executed. + if (ctorRegion) + regions.push_back(RegionSuccessor(ctorRegion)); + if (dtorRegion) + regions.push_back(RegionSuccessor(dtorRegion)); +} + +//===----------------------------------------------------------------------===// +// GetGlobalOp +//===----------------------------------------------------------------------===// + +LogicalResult +GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + // Verify that the result type underlying pointer type matches the type of the + // referenced cir.global or cir.func op. + auto op = symbolTable.lookupNearestSymbolFrom(*this, getNameAttr()); + if (!(isa(op) || isa(op))) + return emitOpError("'") + << getName() + << "' does not reference a valid cir.global or cir.func"; + + mlir::Type symTy; + if (auto g = dyn_cast(op)) + symTy = g.getSymType(); + else if (auto f = dyn_cast(op)) + symTy = f.getFunctionType(); + else + llvm_unreachable("shall not get here"); + + auto resultType = getAddr().getType().dyn_cast(); + if (!resultType || symTy != resultType.getPointee()) + return emitOpError("result type pointee type '") + << resultType.getPointee() << "' does not match type " << symTy + << " of the global @" << getName(); + return success(); +} + +//===----------------------------------------------------------------------===// +// VTableAddrPointOp +//===----------------------------------------------------------------------===// + +LogicalResult +VTableAddrPointOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + // vtable ptr is not coming from a symbol. + if (!getName()) + return success(); + auto name = *getName(); + + // Verify that the result type underlying pointer type matches the type of the + // referenced cir.global or cir.func op. + auto op = dyn_cast_or_null( + symbolTable.lookupNearestSymbolFrom(*this, getNameAttr())); + if (!op) + return emitOpError("'") + << name << "' does not reference a valid cir.global"; + auto init = op.getInitialValue(); + if (!init) + return success(); + if (!isa(*init)) + return emitOpError("Expected #cir.vtable in initializer for global '") + << name << "'"; + return success(); +} + +LogicalResult cir::VTableAddrPointOp::verify() { + // The operation uses either a symbol or a value to operate, but not both + if (getName() && getSymAddr()) + return emitOpError("should use either a symbol or value, but not both"); + + // If not a symbol, stick with the concrete type used for getSymAddr. + if (getSymAddr()) + return success(); + + auto resultType = getAddr().getType(); + auto intTy = mlir::cir::IntType::get(getContext(), 32, /*isSigned=*/false); + auto fnTy = mlir::cir::FuncType::get({}, intTy); + + auto resTy = mlir::cir::PointerType::get( + getContext(), mlir::cir::PointerType::get(getContext(), fnTy)); + + if (resultType != resTy) + return emitOpError("result type must be '") + << resTy << "', but provided result type is '" << resultType << "'"; + return success(); +} + +//===----------------------------------------------------------------------===// +// FuncOp +//===----------------------------------------------------------------------===// + +/// Returns the name used for the linkage attribute. This *must* correspond to +/// the name of the attribute in ODS. +static StringRef getLinkageAttrNameString() { return "linkage"; } + +void cir::FuncOp::build(OpBuilder &builder, OperationState &result, + StringRef name, cir::FuncType type, + GlobalLinkageKind linkage, + ArrayRef attrs, + ArrayRef argAttrs) { + result.addRegion(); + result.addAttribute(SymbolTable::getSymbolAttrName(), + builder.getStringAttr(name)); + result.addAttribute(getFunctionTypeAttrName(result.name), + TypeAttr::get(type)); + result.addAttribute( + getLinkageAttrNameString(), + GlobalLinkageKindAttr::get(builder.getContext(), linkage)); + result.attributes.append(attrs.begin(), attrs.end()); + if (argAttrs.empty()) + return; + + function_interface_impl::addArgAndResultAttrs( + builder, result, argAttrs, + /*resultAttrs=*/std::nullopt, getArgAttrsAttrName(result.name), + getResAttrsAttrName(result.name)); +} + +ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) { + llvm::SMLoc loc = parser.getCurrentLocation(); + + auto builtinNameAttr = getBuiltinAttrName(state.name); + auto coroutineNameAttr = getCoroutineAttrName(state.name); + auto lambdaNameAttr = getLambdaAttrName(state.name); + auto visNameAttr = getSymVisibilityAttrName(state.name); + auto noProtoNameAttr = getNoProtoAttrName(state.name); + if (::mlir::succeeded(parser.parseOptionalKeyword(builtinNameAttr.strref()))) + state.addAttribute(builtinNameAttr, parser.getBuilder().getUnitAttr()); + if (::mlir::succeeded( + parser.parseOptionalKeyword(coroutineNameAttr.strref()))) + state.addAttribute(coroutineNameAttr, parser.getBuilder().getUnitAttr()); + if (::mlir::succeeded(parser.parseOptionalKeyword(lambdaNameAttr.strref()))) + state.addAttribute(lambdaNameAttr, parser.getBuilder().getUnitAttr()); + if (parser.parseOptionalKeyword(noProtoNameAttr).succeeded()) + state.addAttribute(noProtoNameAttr, parser.getBuilder().getUnitAttr()); + + // Default to external linkage if no keyword is provided. + state.addAttribute(getLinkageAttrNameString(), + GlobalLinkageKindAttr::get( + parser.getContext(), + parseOptionalCIRKeyword( + parser, GlobalLinkageKind::ExternalLinkage))); + + ::llvm::StringRef visAttrStr; + if (parser.parseOptionalKeyword(&visAttrStr, {"private", "public", "nested"}) + .succeeded()) { + state.addAttribute(visNameAttr, + parser.getBuilder().getStringAttr(visAttrStr)); + } + + StringAttr nameAttr; + SmallVector arguments; + SmallVector argAttrs; + SmallVector resultAttrs; + SmallVector argTypes; + SmallVector resultTypes; + auto &builder = parser.getBuilder(); + + // Parse the name as a symbol. + if (parser.parseSymbolName(nameAttr, SymbolTable::getSymbolAttrName(), + state.attributes)) + return failure(); + + // Parse the function signature. + bool isVariadic = false; + if (function_interface_impl::parseFunctionSignature( + parser, /*allowVariadic=*/true, arguments, isVariadic, resultTypes, + resultAttrs)) + return failure(); + + for (auto &arg : arguments) + argTypes.push_back(arg.type); + + if (resultTypes.size() > 1) + return parser.emitError(loc, "functions only supports zero or one results"); + + // Fetch return type or set it to void if empty/ommited. + mlir::Type returnType = + (resultTypes.empty() ? mlir::cir::VoidType::get(builder.getContext()) + : resultTypes.front()); + + // Build the function type. + auto fnType = mlir::cir::FuncType::get(argTypes, returnType, isVariadic); + if (!fnType) + return failure(); + state.addAttribute(getFunctionTypeAttrName(state.name), + TypeAttr::get(fnType)); + + // If additional attributes are present, parse them. + if (parser.parseOptionalAttrDictWithKeyword(state.attributes)) + return failure(); + + // Add the attributes to the function arguments. + assert(resultAttrs.size() == resultTypes.size()); + function_interface_impl::addArgAndResultAttrs( + builder, state, arguments, resultAttrs, getArgAttrsAttrName(state.name), + getResAttrsAttrName(state.name)); + + bool hasAlias = false; + auto aliaseeNameAttr = getAliaseeAttrName(state.name); + if (::mlir::succeeded(parser.parseOptionalKeyword("alias"))) { + if (parser.parseLParen().failed()) + return failure(); + StringAttr aliaseeAttr; + if (parser.parseOptionalSymbolName(aliaseeAttr).failed()) + return failure(); + state.addAttribute(aliaseeNameAttr, FlatSymbolRefAttr::get(aliaseeAttr)); + if (parser.parseRParen().failed()) + return failure(); + hasAlias = true; + } + + // If extra func attributes are present, parse them. + NamedAttrList extraAttrs; + if (::mlir::succeeded(parser.parseOptionalKeyword("extra"))) { + if (parser.parseLParen().failed()) + return failure(); + if (parser.parseOptionalAttrDict(extraAttrs).failed()) + return failure(); + if (parser.parseRParen().failed()) + return failure(); + } + state.addAttribute(getExtraAttrsAttrName(state.name), + mlir::cir::ExtraFuncAttributesAttr::get( + builder.getContext(), + extraAttrs.getDictionary(builder.getContext()))); + + // Parse the optional function body. + auto *body = state.addRegion(); + OptionalParseResult parseResult = parser.parseOptionalRegion( + *body, arguments, /*enableNameShadowing=*/false); + if (parseResult.has_value()) { + if (hasAlias) + parser.emitError(loc, "function alias shall not have a body"); + if (failed(*parseResult)) + return failure(); + // Function body was parsed, make sure its not empty. + if (body->empty()) + return parser.emitError(loc, "expected non-empty function body"); + } + return success(); +} + +bool cir::FuncOp::isDeclaration() { + auto aliasee = getAliasee(); + if (!aliasee) + return isExternal(); + + auto *modOp = getOperation()->getParentOp(); + auto targetFn = dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(modOp, *aliasee)); + assert(targetFn && "expected aliasee to exist"); + return targetFn.isDeclaration(); +} + +::mlir::Region *cir::FuncOp::getCallableRegion() { + auto aliasee = getAliasee(); + if (!aliasee) + return isExternal() ? nullptr : &getBody(); + + // Note that we forward the region from the original aliasee + // function. + auto *modOp = getOperation()->getParentOp(); + auto targetFn = dyn_cast_or_null( + mlir::SymbolTable::lookupSymbolIn(modOp, *aliasee)); + assert(targetFn && "expected aliasee to exist"); + return targetFn.getCallableRegion(); +} + +void cir::FuncOp::print(OpAsmPrinter &p) { + p << ' '; + + if (getBuiltin()) + p << "builtin "; + + if (getCoroutine()) + p << "coroutine "; + + if (getLambda()) + p << "lambda "; + + if (getNoProto()) + p << "no_proto "; + + if (getLinkage() != GlobalLinkageKind::ExternalLinkage) + p << stringifyGlobalLinkageKind(getLinkage()) << ' '; + + auto vis = getVisibility(); + if (vis != mlir::SymbolTable::Visibility::Public) + p << vis << " "; + + // Print function name, signature, and control. + p.printSymbolName(getSymName()); + auto fnType = getFunctionType(); + SmallVector resultTypes; + if (!fnType.isVoid()) + function_interface_impl::printFunctionSignature( + p, *this, fnType.getInputs(), fnType.isVarArg(), + fnType.getReturnTypes()); + else + function_interface_impl::printFunctionSignature( + p, *this, fnType.getInputs(), fnType.isVarArg(), {}); + function_interface_impl::printFunctionAttributes( + p, *this, + {getSymVisibilityAttrName(), getAliaseeAttrName(), + getFunctionTypeAttrName(), getLinkageAttrName(), getBuiltinAttrName(), + getNoProtoAttrName(), getExtraAttrsAttrName()}); + + if (auto aliaseeName = getAliasee()) { + p << " alias("; + p.printSymbolName(*aliaseeName); + p << ")"; + } + + if (!getExtraAttrs().getElements().empty()) { + p << " extra("; + p.printOptionalAttrDict(getExtraAttrs().getElements().getValue()); + p << " )"; + } + + // Print the body if this is not an external function. + Region &body = getOperation()->getRegion(0); + if (!body.empty()) { + p << ' '; + p.printRegion(body, /*printEntryBlockArgs=*/false, + /*printBlockTerminators=*/true); + } +} + +// Hook for OpTrait::FunctionLike, called after verifying that the 'type' +// attribute is present. This can check for preconditions of the +// getNumArguments hook not failing. +LogicalResult cir::FuncOp::verifyType() { + auto type = getFunctionType(); + if (!type.isa()) + return emitOpError("requires '" + getFunctionTypeAttrName().str() + + "' attribute of function type"); + if (!getNoProto() && type.isVarArg() && type.getNumInputs() == 0) + return emitError() + << "prototyped function must have at least one non-variadic input"; + return success(); +} + +// Verifies linkage types +// - functions don't have 'common' linkage +// - external functions have 'external' or 'extern_weak' linkage +// - coroutine body must use at least one cir.await operation. +LogicalResult cir::FuncOp::verify() { + if (getLinkage() == cir::GlobalLinkageKind::CommonLinkage) + return emitOpError() << "functions cannot have '" + << stringifyGlobalLinkageKind( + cir::GlobalLinkageKind::CommonLinkage) + << "' linkage"; + + if (isExternal()) { + if (getLinkage() != cir::GlobalLinkageKind::ExternalLinkage && + getLinkage() != cir::GlobalLinkageKind::ExternalWeakLinkage) + return emitOpError() << "external functions must have '" + << stringifyGlobalLinkageKind( + cir::GlobalLinkageKind::ExternalLinkage) + << "' or '" + << stringifyGlobalLinkageKind( + cir::GlobalLinkageKind::ExternalWeakLinkage) + << "' linkage"; + return success(); + } + + if (!isDeclaration() && getCoroutine()) { + bool foundAwait = false; + this->walk([&](Operation *op) { + if (auto await = dyn_cast(op)) { + foundAwait = true; + return; + } + }); + if (!foundAwait) + return emitOpError() + << "coroutine body must use at least one cir.await op"; + } + + // Function alias should have an empty body. + if (auto fn = getAliasee()) { + if (fn && !getBody().empty()) + return emitOpError() << "a function alias '" << *fn + << "' must have empty body"; + } + return success(); +} + +//===----------------------------------------------------------------------===// +// CallOp +//===----------------------------------------------------------------------===// + +/// Get the argument operands to the called function. +OperandRange cir::CallOp::getArgOperands() { + return {arg_operand_begin(), arg_operand_end()}; +} + +MutableOperandRange cir::CallOp::getArgOperandsMutable() { + return getOperandsMutable(); +} + +/// Return the callee of this operation. +CallInterfaceCallable cir::CallOp::getCallableForCallee() { + return (*this)->getAttrOfType("callee"); +} + +/// Set the callee for this operation. +void cir::CallOp::setCalleeFromCallable(mlir::CallInterfaceCallable callee) { + if (auto calling = + (*this)->getAttrOfType(getCalleeAttrName())) + (*this)->setAttr(getCalleeAttrName(), callee.get()); + setOperand(0, callee.get()); +} + +LogicalResult +cir::CallOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + // Callee attribute only need on indirect calls. + auto fnAttr = (*this)->getAttrOfType("callee"); + if (!fnAttr) + return success(); + + FuncOp fn = + symbolTable.lookupNearestSymbolFrom(*this, fnAttr); + if (!fn) + return emitOpError() << "'" << fnAttr.getValue() + << "' does not reference a valid function"; + + // Verify that the operand and result types match the callee. Note that + // argument-checking is disabled for functions without a prototype. + auto fnType = fn.getFunctionType(); + if (!fn.getNoProto()) { + if (!fnType.isVarArg() && getNumOperands() != fnType.getNumInputs()) + return emitOpError("incorrect number of operands for callee"); + + if (fnType.isVarArg() && getNumOperands() < fnType.getNumInputs()) + return emitOpError("too few operands for callee"); + + for (unsigned i = 0, e = fnType.getNumInputs(); i != e; ++i) + if (getOperand(i).getType() != fnType.getInput(i)) + return emitOpError("operand type mismatch: expected operand type ") + << fnType.getInput(i) << ", but provided " + << getOperand(i).getType() << " for operand number " << i; + } + + // Void function must not return any results. + if (fnType.isVoid() && getNumResults() != 0) + return emitOpError("callee returns void but call has results"); + + // Non-void function calls must return exactly one result. + if (!fnType.isVoid() && getNumResults() != 1) + return emitOpError("incorrect number of results for callee"); + + // Parent function and return value types must match. + if (!fnType.isVoid() && getResultTypes().front() != fnType.getReturnType()) { + return emitOpError("result type mismatch: expected ") + << fnType.getReturnType() << ", but provided " + << getResult(0).getType(); + } + + return success(); +} + +::mlir::ParseResult CallOp::parse(::mlir::OpAsmParser &parser, + ::mlir::OperationState &result) { + mlir::FlatSymbolRefAttr calleeAttr; + llvm::SmallVector<::mlir::OpAsmParser::UnresolvedOperand, 4> ops; + llvm::SMLoc opsLoc; + (void)opsLoc; + llvm::ArrayRef<::mlir::Type> operandsTypes; + llvm::ArrayRef<::mlir::Type> allResultTypes; + + // If we cannot parse a string callee, it means this is an indirect call. + if (!parser.parseOptionalAttribute(calleeAttr, "callee", result.attributes) + .has_value()) { + OpAsmParser::UnresolvedOperand indirectVal; + // Do not resolve right now, since we need to figure out the type + if (parser.parseOperand(indirectVal).failed()) + return failure(); + ops.push_back(indirectVal); + } + + if (parser.parseLParen()) + return ::mlir::failure(); + + opsLoc = parser.getCurrentLocation(); + if (parser.parseOperandList(ops)) + return ::mlir::failure(); + if (parser.parseRParen()) + return ::mlir::failure(); + if (parser.parseOptionalAttrDict(result.attributes)) + return ::mlir::failure(); + if (parser.parseColon()) + return ::mlir::failure(); + + ::mlir::FunctionType opsFnTy; + if (parser.parseType(opsFnTy)) + return ::mlir::failure(); + operandsTypes = opsFnTy.getInputs(); + allResultTypes = opsFnTy.getResults(); + result.addTypes(allResultTypes); + if (parser.resolveOperands(ops, operandsTypes, opsLoc, result.operands)) + return ::mlir::failure(); + return ::mlir::success(); +} + +void CallOp::print(::mlir::OpAsmPrinter &state) { + state << ' '; + auto ops = getOperands(); + + if (getCallee()) { // Direct calls + state.printAttributeWithoutType(getCalleeAttr()); + } else { // Indirect calls + state << ops.front(); + ops = ops.drop_front(); + } + state << "("; + state << ops; + state << ")"; + llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs; + elidedAttrs.push_back("callee"); + state.printOptionalAttrDict((*this)->getAttrs(), elidedAttrs); + state << ' ' << ":"; + state << ' '; + state.printFunctionalType(getOperands().getTypes(), + getOperation()->getResultTypes()); +} + +//===----------------------------------------------------------------------===// +// UnaryOp +//===----------------------------------------------------------------------===// + +LogicalResult UnaryOp::verify() { + switch (getKind()) { + case cir::UnaryOpKind::Inc: + LLVM_FALLTHROUGH; + case cir::UnaryOpKind::Dec: { + // TODO: Consider looking at the memory interface instead of LoadOp/StoreOp. + auto loadOp = getInput().getDefiningOp(); + if (!loadOp) + return emitOpError() << "requires input to be defined by a memory load"; + + for (const auto user : getResult().getUsers()) { + auto storeOp = dyn_cast(user); + if (storeOp && storeOp.getAddr() == loadOp.getAddr()) + return success(); + } + return emitOpError() << "requires result to be used by a memory store " + "to the same address as the input memory load"; + } + case cir::UnaryOpKind::Plus: + case cir::UnaryOpKind::Minus: + case cir::UnaryOpKind::Not: + // Nothing to verify. + return success(); + } + + llvm_unreachable("Unknown UnaryOp kind?"); +} + +//===----------------------------------------------------------------------===// +// AwaitOp +//===----------------------------------------------------------------------===// + +void AwaitOp::build(OpBuilder &builder, OperationState &result, + mlir::cir::AwaitKind kind, + function_ref readyBuilder, + function_ref suspendBuilder, + function_ref resumeBuilder) { + result.addAttribute(getKindAttrName(result.name), + cir::AwaitKindAttr::get(builder.getContext(), kind)); + { + OpBuilder::InsertionGuard guard(builder); + Region *readyRegion = result.addRegion(); + builder.createBlock(readyRegion); + readyBuilder(builder, result.location); + } + + { + OpBuilder::InsertionGuard guard(builder); + Region *suspendRegion = result.addRegion(); + builder.createBlock(suspendRegion); + suspendBuilder(builder, result.location); + } + + { + OpBuilder::InsertionGuard guard(builder); + Region *resumeRegion = result.addRegion(); + builder.createBlock(resumeRegion); + resumeBuilder(builder, result.location); + } +} + +/// Given the region at `index`, or the parent operation if `index` is None, +/// return the successor regions. These are the regions that may be selected +/// during the flow of control. `operands` is a set of optional attributes +/// that correspond to a constant value for each operand, or null if that +/// operand is not a constant. +void AwaitOp::getSuccessorRegions(mlir::RegionBranchPoint point, + SmallVectorImpl ®ions) { + // If any index all the underlying regions branch back to the parent + // operation. + if (!point.isParent()) { + regions.push_back(RegionSuccessor()); + return; + } + + // FIXME: we want to look at cond region for getting more accurate results + // if the other regions will get a chance to execute. + regions.push_back(RegionSuccessor(&this->getReady())); + regions.push_back(RegionSuccessor(&this->getSuspend())); + regions.push_back(RegionSuccessor(&this->getResume())); +} + +LogicalResult AwaitOp::verify() { return success(); } + +//===----------------------------------------------------------------------===// +// CIR defined traits +//===----------------------------------------------------------------------===// + +LogicalResult +mlir::OpTrait::impl::verifySameFirstOperandAndResultType(Operation *op) { + if (failed(verifyAtLeastNOperands(op, 1)) || failed(verifyOneResult(op))) + return failure(); + + auto type = op->getResult(0).getType(); + auto opType = op->getOperand(0).getType(); + + if (type != opType) + return op->emitOpError() + << "requires the same type for first operand and result"; + + return success(); +} + +//===----------------------------------------------------------------------===// +// CIR attributes +// FIXME: move all of these to CIRAttrs.cpp +//===----------------------------------------------------------------------===// + +LogicalResult mlir::cir::ConstArrayAttr::verify( + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + ::mlir::Type type, Attribute attr) { + + if (!(attr.isa() || attr.isa())) + return emitError() << "constant array expects ArrayAttr or StringAttr"; + + if (auto strAttr = attr.dyn_cast()) { + mlir::cir::ArrayType at = type.cast(); + auto intTy = at.getEltType().dyn_cast(); + + // TODO: add CIR type for char. + if (!intTy || intTy.getWidth() != 8) { + emitError() << "constant array element for string literals expects " + "!cir.int element type"; + return failure(); + } + return success(); + } + + assert(attr.isa()); + auto arrayAttr = attr.cast(); + auto at = type.cast(); + + // Make sure both number of elements and subelement types match type. + if (at.getSize() != arrayAttr.size()) + return emitError() << "constant array size should match type size"; + LogicalResult eltTypeCheck = success(); + arrayAttr.walkImmediateSubElements( + [&](Attribute attr) { + // Once we find a mismatch, stop there. + if (eltTypeCheck.failed()) + return; + auto typedAttr = attr.dyn_cast(); + if (!typedAttr || typedAttr.getType() != at.getEltType()) { + eltTypeCheck = failure(); + emitError() + << "constant array element should match array element type"; + } + }, + [&](Type type) {}); + return eltTypeCheck; +} + +::mlir::Attribute ConstArrayAttr::parse(::mlir::AsmParser &parser, + ::mlir::Type type) { + ::mlir::FailureOr<::mlir::Type> resultTy; + ::mlir::FailureOr resultVal; + ::llvm::SMLoc loc = parser.getCurrentLocation(); + (void)loc; + // Parse literal '<' + if (parser.parseLess()) + return {}; + + // Parse variable 'value' + resultVal = ::mlir::FieldParser::parse(parser); + if (failed(resultVal)) { + parser.emitError( + parser.getCurrentLocation(), + "failed to parse ConstArrayAttr parameter 'value' which is " + "to be a `Attribute`"); + return {}; + } + + // ArrayAttrrs have per-element type, not the type of the array... + if (resultVal->dyn_cast()) { + // Array has implicit type: infer from const array type. + if (parser.parseOptionalColon().failed()) { + resultTy = type; + } else { // Array has explicit type: parse it. + resultTy = ::mlir::FieldParser<::mlir::Type>::parse(parser); + if (failed(resultTy)) { + parser.emitError( + parser.getCurrentLocation(), + "failed to parse ConstArrayAttr parameter 'type' which is " + "to be a `::mlir::Type`"); + return {}; + } + } + } else { + assert(resultVal->isa() && "IDK"); + auto ta = resultVal->cast(); + resultTy = ta.getType(); + if (resultTy->isa()) { + parser.emitError(parser.getCurrentLocation(), + "expected type declaration for string literal"); + return {}; + } + } + + // Parse literal '>' + if (parser.parseGreater()) + return {}; + return parser.getChecked(loc, parser.getContext(), + resultTy.value(), resultVal.value()); +} + +void ConstArrayAttr::print(::mlir::AsmPrinter &printer) const { + printer << "<"; + printer.printStrippedAttrOrType(getElts()); + printer << ">"; +} + +::mlir::Attribute SignedOverflowBehaviorAttr::parse(::mlir::AsmParser &parser, + ::mlir::Type type) { + if (parser.parseLess()) + return {}; + auto behavior = parseOptionalCIRKeyword( + parser, mlir::cir::sob::SignedOverflowBehavior::undefined); + if (parser.parseGreater()) + return {}; + + return SignedOverflowBehaviorAttr::get(parser.getContext(), behavior); +} + +void SignedOverflowBehaviorAttr::print(::mlir::AsmPrinter &printer) const { + printer << "<"; + switch (getBehavior()) { + case sob::SignedOverflowBehavior::undefined: + printer << "undefined"; + break; + case sob::SignedOverflowBehavior::defined: + printer << "defined"; + break; + case sob::SignedOverflowBehavior::trapping: + printer << "trapping"; + break; + } + printer << ">"; +} + +LogicalResult TypeInfoAttr::verify( + ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + ::mlir::Type type, ::mlir::ArrayAttr typeinfoData) { + + if (mlir::cir::ConstStructAttr::verify(emitError, type, typeinfoData) + .failed()) + return failure(); + + for (auto &member : typeinfoData) { + if (llvm::isa(member)) + continue; + emitError() << "expected GlobalViewAttr or IntAttr attribute"; + return failure(); + } + + return success(); +} + +LogicalResult +VTableAttr::verify(::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError, + ::mlir::Type type, ::mlir::ArrayAttr vtableData) { + auto sTy = type.dyn_cast_or_null(); + if (!sTy) { + emitError() << "expected !cir.struct type result"; + return failure(); + } + if (sTy.getMembers().size() != 1 || vtableData.size() != 1) { + emitError() << "expected struct type with only one subtype"; + return failure(); + } + + auto arrayTy = sTy.getMembers()[0].dyn_cast(); + auto constArrayAttr = vtableData[0].dyn_cast(); + if (!arrayTy || !constArrayAttr) { + emitError() << "expected struct type with one array element"; + return failure(); + } + + if (mlir::cir::ConstStructAttr::verify(emitError, type, vtableData).failed()) + return failure(); + + LogicalResult eltTypeCheck = success(); + if (auto arrayElts = constArrayAttr.getElts().dyn_cast()) { + arrayElts.walkImmediateSubElements( + [&](Attribute attr) { + if (attr.isa() || attr.isa()) + return; + emitError() << "expected GlobalViewAttr attribute"; + eltTypeCheck = failure(); + }, + [&](Type type) {}); + return eltTypeCheck; + } + + return success(); +} + +//===----------------------------------------------------------------------===// +// CopyOp Definitions +//===----------------------------------------------------------------------===// + +LogicalResult CopyOp::verify() { + + // A data layout is required for us to know the number of bytes to be copied. + if (!getType().getPointee().hasTrait()) + return emitError() << "missing data layout for pointee type"; + + if (getSrc() == getDst()) + return emitError() << "source and destination are the same"; + + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// MemCpyOp Definitions +//===----------------------------------------------------------------------===// + +LogicalResult MemCpyOp::verify() { + auto voidPtr = + cir::PointerType::get(getContext(), cir::VoidType::get(getContext())); + + if (!getLenTy().isUnsigned()) + return emitError() << "memcpy length must be an unsigned integer"; + + if (getSrcTy() != voidPtr || getDstTy() != voidPtr) + return emitError() << "memcpy src and dst must be void pointers"; + + return mlir::success(); +} + +static bool isIncompleteType(mlir::Type typ) { + if (auto ptr = typ.dyn_cast()) + return isIncompleteType(ptr.getPointee()); + else if (auto rec = typ.dyn_cast()) + return rec.isIncomplete(); + return false; +} + +//===----------------------------------------------------------------------===// +// GetMemberOp Definitions +//===----------------------------------------------------------------------===// + +LogicalResult GetMemberOp::verify() { + + const auto recordTy = getAddrTy().getPointee().dyn_cast(); + if (!recordTy) + return emitError() << "expected pointer to a record type"; + + // FIXME: currently we bypass typechecking of incomplete types due to errors + // in the codegen process. This should be removed once the codegen is fixed. + if (isIncompleteType(recordTy)) + return mlir::success(); + + if (recordTy.getMembers().size() <= getIndex()) + return emitError() << "member index out of bounds"; + + // FIXME(cir): member type check is disabled for classes as the codegen for + // these still need to be patched. + // Also we bypass the typechecking for the fields of incomplete types. + bool shouldSkipMemberTypeMismatch = + recordTy.isClass() || isIncompleteType(recordTy.getMembers()[getIndex()]); + + if (!shouldSkipMemberTypeMismatch + && recordTy.getMembers()[getIndex()] != getResultTy().getPointee()) + return emitError() << "member type mismatch"; + + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// TableGen'd op method definitions +//===----------------------------------------------------------------------===// + +#define GET_OP_CLASSES +#include "clang/CIR/Dialect/IR/CIROps.cpp.inc" diff --git a/clang/lib/CIR/Dialect/IR/CIRTypes.cpp b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp new file mode 100644 index 000000000000..be5d6bc9cd81 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/CIRTypes.cpp @@ -0,0 +1,510 @@ +//===- CIRTypes.cpp - MLIR CIR Types --------------------------------------===// +// +// 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 defines the types in the CIR dialect. +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +#include "mlir/IR/Attributes.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/BuiltinTypes.h" +#include "mlir/IR/DialectImplementation.h" +#include "mlir/Interfaces/DataLayoutInterfaces.h" +#include "mlir/Support/LogicalResult.h" + +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/TypeSwitch.h" +#include "llvm/Support/ErrorHandling.h" +#include + +//===----------------------------------------------------------------------===// +// CIR Custom Parser/Printer Signatures +//===----------------------------------------------------------------------===// + +static mlir::ParseResult +parseFuncTypeArgs(mlir::AsmParser &p, llvm::SmallVector ¶ms, + bool &isVarArg); +static void printFuncTypeArgs(mlir::AsmPrinter &p, + mlir::ArrayRef params, bool isVarArg); + +//===----------------------------------------------------------------------===// +// Get autogenerated stuff +//===----------------------------------------------------------------------===// + +#define GET_TYPEDEF_CLASSES +#include "clang/CIR/Dialect/IR/CIROpsTypes.cpp.inc" + +using namespace mlir; +using namespace mlir::cir; + +//===----------------------------------------------------------------------===// +// General CIR parsing / printing +//===----------------------------------------------------------------------===// + +Type CIRDialect::parseType(DialectAsmParser &parser) const { + llvm::SMLoc typeLoc = parser.getCurrentLocation(); + StringRef mnemonic; + Type genType; + OptionalParseResult parseResult = + generatedTypeParser(parser, &mnemonic, genType); + if (parseResult.hasValue()) + return genType; + parser.emitError(typeLoc, "unknown type in CIR dialect"); + return Type(); +} + +void CIRDialect::printType(Type type, DialectAsmPrinter &os) const { + if (failed(generatedTypePrinter(type, os))) + llvm_unreachable("unexpected CIR type kind"); +} + +Type PointerType::parse(mlir::AsmParser &parser) { + if (parser.parseLess()) + return Type(); + Type pointeeType; + if (parser.parseType(pointeeType)) + return Type(); + if (parser.parseGreater()) + return Type(); + return get(parser.getContext(), pointeeType); +} + +void PointerType::print(mlir::AsmPrinter &printer) const { + printer << "<"; + printer.printType(getPointee()); + printer << '>'; +} + +Type BoolType::parse(mlir::AsmParser &parser) { + return get(parser.getContext()); +} + +void BoolType::print(mlir::AsmPrinter &printer) const {} + +//===----------------------------------------------------------------------===// +// StructType Definitions +//===----------------------------------------------------------------------===// + +/// Return the largest member of in the type. +/// +/// Recurses into union members never returning a union as the largest member. +Type StructType::getLargestMember(const ::mlir::DataLayout &dataLayout) const { + if (!largestMember) + computeSizeAndAlignment(dataLayout); + return largestMember; +} + +Type StructType::parse(mlir::AsmParser &parser) { + const auto loc = parser.getCurrentLocation(); + bool packed = false; + RecordKind kind; + + if (parser.parseLess()) + return {}; + + // TODO(cir): in the future we should probably separate types for different + // source language declarations such as cir.class, cir.union, and cir.struct + if (parser.parseOptionalKeyword("struct").succeeded()) + kind = RecordKind::Struct; + else if (parser.parseOptionalKeyword("union").succeeded()) + kind = RecordKind::Union; + else if (parser.parseOptionalKeyword("class").succeeded()) + kind = RecordKind::Class; + else { + parser.emitError(loc, "unknown struct type"); + return {}; + } + + mlir::StringAttr name; + parser.parseOptionalAttribute(name); + + if (parser.parseOptionalKeyword("packed").succeeded()) + packed = true; + + // Parse record members or lack thereof. + bool incomplete = true; + llvm::SmallVector members; + if (parser.parseOptionalKeyword("incomplete").failed()) { + incomplete = false; + const auto delimiter = AsmParser::Delimiter::Braces; + const auto parseElementFn = [&parser, &members]() { + return parser.parseType(members.emplace_back()); + }; + if (parser.parseCommaSeparatedList(delimiter, parseElementFn).failed()) + return {}; + } + + // Parse optional AST attribute. This is just a formality for now, since CIR + // cannot yet read serialized AST. + mlir::cir::ASTRecordDeclAttr ast = nullptr; + parser.parseOptionalAttribute(ast); + + if (parser.parseGreater()) + return {}; + + return StructType::get(parser.getContext(), members, name, incomplete, packed, + kind, nullptr); +} + +void StructType::print(mlir::AsmPrinter &printer) const { + printer << '<'; + + switch (getKind()) { + case RecordKind::Struct: + printer << "struct "; + break; + case RecordKind::Union: + printer << "union "; + break; + case RecordKind::Class: + printer << "class "; + break; + } + + if (getName()) + printer << getName() << " "; + + if (getPacked()) + printer << "packed "; + + if (isIncomplete()) { + printer << "incomplete"; + } else { + printer << "{"; + llvm::interleaveComma(getMembers(), printer); + printer << "}"; + } + + if (getAst()) { + printer << " "; + printer.printAttribute(getAst()); + } + + printer << '>'; +} + +//===----------------------------------------------------------------------===// +// Data Layout information for types +//===----------------------------------------------------------------------===// + +unsigned +BoolType::getTypeSizeInBits(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return 8; +} + +unsigned +BoolType::getABIAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return 1; +} + +unsigned +BoolType::getPreferredAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return 1; +} + +unsigned +PointerType::getTypeSizeInBits(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + // FIXME: improve this in face of address spaces + return 64; +} + +unsigned +PointerType::getABIAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + // FIXME: improve this in face of address spaces + return 8; +} + +unsigned PointerType::getPreferredAlignment( + const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + // FIXME: improve this in face of address spaces + return 8; +} + +unsigned +ArrayType::getTypeSizeInBits(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return getSize() * dataLayout.getTypeSizeInBits(getEltType()); +} + +unsigned +ArrayType::getABIAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return dataLayout.getTypeABIAlignment(getEltType()); +} + +unsigned +ArrayType::getPreferredAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return dataLayout.getTypePreferredAlignment(getEltType()); +} + +unsigned +StructType::getTypeSizeInBits(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + if (!size) + computeSizeAndAlignment(dataLayout); + return *size * 8; +} + +unsigned +StructType::getABIAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + if (!align) + computeSizeAndAlignment(dataLayout); + return *align; +} + +unsigned +StructType::getPreferredAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + llvm_unreachable("NYI"); +} + +bool StructType::isPadded(const ::mlir::DataLayout &dataLayout) const { + if (!padded) + computeSizeAndAlignment(dataLayout); + return *padded; +} + +void StructType::computeSizeAndAlignment( + const ::mlir::DataLayout &dataLayout) const { + assert(isComplete() && "Cannot get layout of incomplete structs"); + // Do not recompute. + if (size || align || padded || largestMember) + return; + + // This is a similar algorithm to LLVM's StructLayout. + unsigned structSize = 0; + llvm::Align structAlignment{1}; + [[maybe_unused]] bool isPadded = false; + unsigned numElements = getNumElements(); + auto members = getMembers(); + unsigned largestMemberSize = 0; + + // Loop over each of the elements, placing them in memory. + for (unsigned i = 0, e = numElements; i != e; ++i) { + auto ty = members[i]; + + // Found a nested union: recurse into it to fetch its largest member. + auto structMember = ty.dyn_cast(); + if (structMember && structMember.isUnion()) { + auto candidate = structMember.getLargestMember(dataLayout); + if (dataLayout.getTypeSize(candidate) > largestMemberSize) { + largestMember = candidate; + largestMemberSize = dataLayout.getTypeSize(largestMember); + } + } else if (dataLayout.getTypeSize(ty) > largestMemberSize) { + largestMember = ty; + largestMemberSize = dataLayout.getTypeSize(largestMember); + } + + // This matches LLVM since it uses the ABI instead of preferred alignment. + const llvm::Align tyAlign = + llvm::Align(getPacked() ? 1 : dataLayout.getTypeABIAlignment(ty)); + + // Add padding if necessary to align the data element properly. + if (!llvm::isAligned(tyAlign, structSize)) { + isPadded = true; + structSize = llvm::alignTo(structSize, tyAlign); + } + + // Keep track of maximum alignment constraint. + structAlignment = std::max(tyAlign, structAlignment); + + // FIXME: track struct size up to each element. + // getMemberOffsets()[i] = structSize; + + // Consume space for this data item + structSize += dataLayout.getTypeSize(ty); + } + + // For unions, the size and aligment is that of the largest element. + if (isUnion()) { + size = largestMemberSize; + align = structAlignment.value(); + padded = false; + return; + } + + // Add padding to the end of the struct so that it could be put in an array + // and all array elements would be aligned correctly. + if (!llvm::isAligned(structAlignment, structSize)) { + isPadded = true; + structSize = llvm::alignTo(structSize, structAlignment); + } + + size = structSize; + align = structAlignment.value(); + padded = isPadded; +} + +//===----------------------------------------------------------------------===// +// IntType Definitions +//===----------------------------------------------------------------------===// + +Type IntType::parse(mlir::AsmParser &parser) { + auto *context = parser.getBuilder().getContext(); + auto loc = parser.getCurrentLocation(); + bool isSigned; + unsigned width; + + if (parser.parseLess()) + return {}; + + // Fetch integer sign. + llvm::StringRef sign; + if (parser.parseKeyword(&sign)) + return {}; + if (sign.equals("s")) + isSigned = true; + else if (sign.equals("u")) + isSigned = false; + else { + parser.emitError(loc, "expected 's' or 'u'"); + return {}; + } + + if (parser.parseComma()) + return {}; + + // Fetch integer size. + if (parser.parseInteger(width)) + return {}; + if (width % 8 != 0) { + parser.emitError(loc, "expected integer width to be a multiple of 8"); + return {}; + } + if (width < 8 || width > 64) { + parser.emitError(loc, "expected integer width to be from 8 up to 64"); + return {}; + } + + if (parser.parseGreater()) + return {}; + + return IntType::get(context, width, isSigned); +} + +void IntType::print(mlir::AsmPrinter &printer) const { + auto sign = isSigned() ? 's' : 'u'; + printer << '<' << sign << ", " << getWidth() << '>'; +} + +unsigned IntType::getTypeSizeInBits(const mlir::DataLayout &dataLayout, + mlir::DataLayoutEntryListRef params) const { + return getWidth(); +} + +unsigned IntType::getABIAlignment(const mlir::DataLayout &dataLayout, + mlir::DataLayoutEntryListRef params) const { + return (unsigned)(getWidth() / 8); +} + +unsigned +IntType::getPreferredAlignment(const ::mlir::DataLayout &dataLayout, + ::mlir::DataLayoutEntryListRef params) const { + return (unsigned)(getWidth() / 8); +} + +mlir::LogicalResult +IntType::verify(llvm::function_ref emitError, + unsigned width, bool isSigned) { + + if (width < 8 || width > 64) { + emitError() << "IntType only supports widths from 8 up to 64"; + return mlir::failure(); + } + if (width % 8 != 0) { + emitError() << "IntType width is not a multiple of 8"; + return mlir::failure(); + } + + return mlir::success(); +} + +//===----------------------------------------------------------------------===// +// FuncType Definitions +//===----------------------------------------------------------------------===// + +FuncType FuncType::clone(TypeRange inputs, TypeRange results) const { + assert(results.size() == 1 && "expected exactly one result type"); + return get(llvm::to_vector(inputs), results[0], isVarArg()); +} + +static mlir::ParseResult +parseFuncTypeArgs(mlir::AsmParser &p, llvm::SmallVector ¶ms, + bool &isVarArg) { + isVarArg = false; + // `(` `)` + if (succeeded(p.parseOptionalRParen())) + return mlir::success(); + + // `(` `...` `)` + if (succeeded(p.parseOptionalEllipsis())) { + isVarArg = true; + return p.parseRParen(); + } + + // type (`,` type)* (`,` `...`)? + mlir::Type type; + if (p.parseType(type)) + return mlir::failure(); + params.push_back(type); + while (succeeded(p.parseOptionalComma())) { + if (succeeded(p.parseOptionalEllipsis())) { + isVarArg = true; + return p.parseRParen(); + } + if (p.parseType(type)) + return mlir::failure(); + params.push_back(type); + } + + return p.parseRParen(); +} + +static void printFuncTypeArgs(mlir::AsmPrinter &p, + mlir::ArrayRef params, + bool isVarArg) { + llvm::interleaveComma(params, p, + [&p](mlir::Type type) { p.printType(type); }); + if (isVarArg) { + if (!params.empty()) + p << ", "; + p << "..."; + } + p << ')'; +} + +llvm::ArrayRef FuncType::getReturnTypes() const { + return static_cast(getImpl())->returnType; +} + +bool FuncType::isVoid() const { return getReturnType().isa(); } + +//===----------------------------------------------------------------------===// +// CIR Dialect +//===----------------------------------------------------------------------===// + +void CIRDialect::registerTypes() { + addTypes< +#define GET_TYPEDEF_LIST +#include "clang/CIR/Dialect/IR/CIROpsTypes.cpp.inc" + >(); +} diff --git a/clang/lib/CIR/Dialect/IR/CMakeLists.txt b/clang/lib/CIR/Dialect/IR/CMakeLists.txt new file mode 100644 index 000000000000..781a401ef657 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/CMakeLists.txt @@ -0,0 +1,20 @@ +add_clang_library(MLIRCIR + CIRAttrs.cpp + CIRDialect.cpp + CIRTypes.cpp + FPEnv.cpp + + DEPENDS + MLIRBuiltinLocationAttributesIncGen + MLIRCIROpsIncGen + MLIRCIREnumsGen + MLIRSymbolInterfacesIncGen + MLIRCIRASTAttrInterfacesIncGen + + LINK_LIBS PUBLIC + MLIRIR + MLIRFuncDialect + MLIRDataLayoutInterfaces + MLIRSideEffectInterfaces + clangAST + ) diff --git a/clang/lib/CIR/Dialect/IR/FPEnv.cpp b/clang/lib/CIR/Dialect/IR/FPEnv.cpp new file mode 100644 index 000000000000..01dfe1e92640 --- /dev/null +++ b/clang/lib/CIR/Dialect/IR/FPEnv.cpp @@ -0,0 +1,64 @@ +//===-- FPEnv.cpp ---- FP Environment -------------------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +/// @file +/// This file contains the implementations of entities that describe floating +/// point environment. +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/IR/FPEnv.h" + +namespace cir { + +std::optional +convertRoundingModeToStr(llvm::RoundingMode UseRounding) { + std::optional RoundingStr; + switch (UseRounding) { + case llvm::RoundingMode::Dynamic: + RoundingStr = "round.dynamic"; + break; + case llvm::RoundingMode::NearestTiesToEven: + RoundingStr = "round.tonearest"; + break; + case llvm::RoundingMode::NearestTiesToAway: + RoundingStr = "round.tonearestaway"; + break; + case llvm::RoundingMode::TowardNegative: + RoundingStr = "round.downward"; + break; + case llvm::RoundingMode::TowardPositive: + RoundingStr = "round.upward"; + break; + case llvm::RoundingMode::TowardZero: + RoundingStr = "round.towardZero"; + break; + default: + break; + } + return RoundingStr; +} + +std::optional +convertExceptionBehaviorToStr(fp::ExceptionBehavior UseExcept) { + std::optional ExceptStr; + switch (UseExcept) { + case fp::ebStrict: + ExceptStr = "fpexcept.strict"; + break; + case fp::ebIgnore: + ExceptStr = "fpexcept.ignore"; + break; + case fp::ebMayTrap: + ExceptStr = "fpexcept.maytrap"; + break; + } + return ExceptStr; +} + +} // namespace cir diff --git a/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt new file mode 100644 index 000000000000..82952f42a2d2 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/CMakeLists.txt @@ -0,0 +1,21 @@ +add_clang_library(MLIRCIRTransforms + LifetimeCheck.cpp + LoweringPrepare.cpp + MergeCleanups.cpp + DropAST.cpp + + DEPENDS + MLIRCIRPassIncGen + + LINK_LIBS PUBLIC + clangAST + clangBasic + + MLIRAnalysis + MLIRIR + MLIRPass + MLIRTransformUtils + + MLIRCIR + MLIRCIRASTAttrInterfaces +) diff --git a/clang/lib/CIR/Dialect/Transforms/DropAST.cpp b/clang/lib/CIR/Dialect/Transforms/DropAST.cpp new file mode 100644 index 000000000000..b72e7a686788 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/DropAST.cpp @@ -0,0 +1,50 @@ +//===- DropAST.cpp - emit diagnostic checks for lifetime violations -===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/CIR/Dialect/Passes.h" + +#include "PassDetail.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "clang/AST/ASTContext.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" + +#include "llvm/ADT/SetOperations.h" +#include "llvm/ADT/SmallSet.h" + +using namespace mlir; +using namespace cir; + +namespace { +struct DropASTPass : public DropASTBase { + DropASTPass() = default; + void runOnOperation() override; +}; +} // namespace + +void DropASTPass::runOnOperation() { + Operation *op = getOperation(); + // This needs to be updated with operations that start + // carrying AST around. + op->walk([&](Operation *op) { + if (auto alloca = dyn_cast(op)) { + alloca.removeAstAttr(); + auto ty = alloca.getAllocaType().dyn_cast(); + if (!ty) + return; + ty.dropAst(); + return; + } + + if (auto funcOp = dyn_cast(op)) + funcOp.removeAstAttr(); + }); +} + +std::unique_ptr mlir::createDropASTPass() { + return std::make_unique(); +} diff --git a/clang/lib/CIR/Dialect/Transforms/LifetimeCheck.cpp b/clang/lib/CIR/Dialect/Transforms/LifetimeCheck.cpp new file mode 100644 index 000000000000..534dfe1808d2 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/LifetimeCheck.cpp @@ -0,0 +1,1983 @@ +//===- Lifetimecheck.cpp - emit diagnostic checks for lifetime violations -===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "PassDetail.h" + +#include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/CIR/Dialect/IR/CIRAttrs.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/Passes.h" + +#include "mlir/Dialect/Func/IR/FuncOps.h" + +#include "llvm/ADT/SetOperations.h" +#include "llvm/ADT/SmallSet.h" + +#include + +using namespace mlir; +using namespace cir; + +namespace { + +struct LocOrdering { + bool operator()(mlir::Location L1, mlir::Location L2) const { + return std::less()(L1.getAsOpaquePointer(), + L2.getAsOpaquePointer()); + } +}; + +struct LifetimeCheckPass : public LifetimeCheckBase { + LifetimeCheckPass() = default; + void runOnOperation() override; + + void checkOperation(Operation *op); + void checkFunc(cir::FuncOp fnOp); + void checkBlock(Block &block); + + void checkRegionWithScope(Region ®ion); + void checkRegion(Region ®ion); + + void checkIf(IfOp op); + void checkSwitch(SwitchOp op); + void checkLoop(LoopOp op); + void checkAlloca(AllocaOp op); + void checkStore(StoreOp op); + void checkLoad(LoadOp op); + void checkCall(CallOp callOp); + void checkAwait(AwaitOp awaitOp); + void checkReturn(ReturnOp retOp); + + void classifyAndInitTypeCategories(mlir::Value addr, mlir::Type t, + mlir::Location loc, unsigned nestLevel); + void updatePointsTo(mlir::Value addr, mlir::Value data, mlir::Location loc); + void updatePointsToForConstStruct(mlir::Value addr, + mlir::cir::ConstStructAttr value, + mlir::Location loc); + void updatePointsToForZeroStruct(mlir::Value addr, StructType sTy, + mlir::Location loc); + + enum DerefStyle { + Direct, + RetLambda, + CallParam, + IndirectCallParam, + }; + void checkPointerDeref(mlir::Value addr, mlir::Location loc, + DerefStyle derefStyle = DerefStyle::Direct); + void checkCoroTaskStore(StoreOp storeOp); + void checkLambdaCaptureStore(StoreOp storeOp); + void trackCallToCoroutine(CallOp callOp); + + void checkCtor(CallOp callOp, ASTCXXConstructorDeclInterface ctor); + void checkMoveAssignment(CallOp callOp, ASTCXXMethodDeclInterface m); + void checkCopyAssignment(CallOp callOp, ASTCXXMethodDeclInterface m); + void checkNonConstUseOfOwner(mlir::Value ownerAddr, mlir::Location loc); + void checkOperators(CallOp callOp, ASTCXXMethodDeclInterface m); + void checkOtherMethodsAndFunctions(CallOp callOp, + ASTCXXMethodDeclInterface m); + void checkForOwnerAndPointerArguments(CallOp callOp, unsigned firstArgIdx); + + // TODO: merge both methods below and pass down an enum. + // + // Check if a method's 'this' pointer (first arg) is tracked as + // a pointer category. Assumes the CallOp in question represents a method + // and returns the actual value associated with the tracked 'this' or an + // empty value if none is found. + mlir::Value getThisParamPointerCategory(CallOp callOp); + // Check if a method's 'this' pointer (first arg) is tracked as + // a owner category. Assumes the CallOp in question represents a method + // and returns the actual value associated with the tracked 'this' or an + // empty value if none is found. + mlir::Value getThisParamOwnerCategory(CallOp callOp); + + // Tracks current module. + ModuleOp theModule; + // Track current function under analysis + std::optional currFunc; + + // Common helpers. + bool isCtorInitPointerFromOwner(CallOp callOp); + mlir::Value getNonConstUseOfOwner(CallOp callOp, ASTCXXMethodDeclInterface m); + bool isOwnerOrPointerClassMethod(CallOp callOp, ASTCXXMethodDeclInterface m); + + // Diagnostic helpers. + void emitInvalidHistory(mlir::InFlightDiagnostic &D, mlir::Value histKey, + mlir::Location warningLoc, + DerefStyle derefStyle = DerefStyle::Direct); + + /// + /// Pass options handling + /// --------------------- + + struct Options { + enum : unsigned { + None = 0, + // Emit pset remarks only detecting invalid derefs + RemarkPsetInvalid = 1, + // Emit pset remarks for all derefs + RemarkPsetAlways = 1 << 1, + RemarkAll = 1 << 2, + HistoryNull = 1 << 3, + HistoryInvalid = 1 << 4, + HistoryAll = 1 << 5, + }; + unsigned val = None; + unsigned histLimit = 1; + bool isOptionsParsed = false; + + void parseOptions(ArrayRef remarks, ArrayRef hist, + unsigned hist_limit) { + if (isOptionsParsed) + return; + + for (auto &remark : remarks) { + val |= StringSwitch(remark) + .Case("pset-invalid", RemarkPsetInvalid) + .Case("pset-always", RemarkPsetAlways) + .Case("all", RemarkAll) + .Default(None); + } + for (auto &h : hist) { + val |= StringSwitch(h) + .Case("invalid", HistoryInvalid) + .Case("null", HistoryNull) + .Case("all", HistoryAll) + .Default(None); + } + histLimit = hist_limit; + isOptionsParsed = true; + } + + void parseOptions(LifetimeCheckPass &pass) { + SmallVector remarks; + SmallVector hists; + + for (auto &r : pass.remarksList) + remarks.push_back(r); + + for (auto &h : pass.historyList) + hists.push_back(h); + + parseOptions(remarks, hists, pass.historyLimit); + } + + bool emitRemarkAll() { return val & RemarkAll; } + bool emitRemarkPsetInvalid() { + return emitRemarkAll() || val & RemarkPsetInvalid; + } + bool emitRemarkPsetAlways() { + return emitRemarkAll() || val & RemarkPsetAlways; + } + + bool emitHistoryAll() { return val & HistoryAll; } + bool emitHistoryNull() { return emitHistoryAll() || val & HistoryNull; } + bool emitHistoryInvalid() { + return emitHistoryAll() || val & HistoryInvalid; + } + } opts; + + /// + /// State + /// ----- + + // Represents the state of an element in a pointer set (pset) + struct State { + using DataTy = enum { + Invalid, + NullPtr, + Global, + // FIXME: currently only supports one level of OwnedBy! + OwnedBy, + LocalValue, + NumKindsMinusOne = LocalValue + }; + State() { val.setInt(Invalid); } + State(DataTy d) { val.setInt(d); } + State(mlir::Value v, DataTy d = LocalValue) { + assert((d == LocalValue || d == OwnedBy) && "expected value or owned"); + val.setPointerAndInt(v, d); + } + + static constexpr int KindBits = 3; + static_assert((1 << KindBits) > NumKindsMinusOne, + "Not enough room for kind!"); + llvm::PointerIntPair val; + + /// Provide less/equal than operator for sorting / set ops. + bool operator<(const State &RHS) const { + // FIXME: note that this makes the ordering non-deterministic, do + // we really care? + if (hasValue() && RHS.hasValue()) + return val.getPointer().getAsOpaquePointer() < + RHS.val.getPointer().getAsOpaquePointer(); + else + return val.getInt() < RHS.val.getInt(); + } + bool operator==(const State &RHS) const { + if (hasValue() && RHS.hasValue()) + return val.getPointer() == RHS.val.getPointer(); + else + return val.getInt() == RHS.val.getInt(); + } + + bool isLocalValue() const { return val.getInt() == LocalValue; } + bool isOwnedBy() const { return val.getInt() == OwnedBy; } + bool hasValue() const { return isLocalValue() || isOwnedBy(); } + + mlir::Value getData() const { + assert(hasValue() && "data type does not hold a mlir::Value"); + return val.getPointer(); + } + + void dump(llvm::raw_ostream &OS = llvm::errs(), int ownedGen = 0); + + static State getInvalid() { return {Invalid}; } + static State getNullPtr() { return {NullPtr}; } + static State getLocalValue(mlir::Value v) { return {v, LocalValue}; } + static State getOwnedBy(mlir::Value v) { return {v, State::OwnedBy}; } + }; + + /// + /// Invalid and null history tracking + /// --------------------------------- + enum InvalidStyle { + Unknown, + EndOfScope, + NotInitialized, + MovedFrom, + NonConstUseOfOwner, + }; + + struct InvalidHistEntry { + InvalidStyle style = Unknown; + std::optional loc; + std::optional val; + InvalidHistEntry() = default; + InvalidHistEntry(InvalidStyle s, std::optional l, + std::optional v) + : style(s), loc(l), val(v) {} + }; + + struct InvalidHist { + llvm::SmallVector entries; + void add(mlir::Value ptr, InvalidStyle invalidStyle, mlir::Location loc, + std::optional val = {}) { + entries.emplace_back(InvalidHistEntry(invalidStyle, loc, val)); + } + }; + + llvm::DenseMap invalidHist; + + using PMapNullHistType = + llvm::DenseMap>; + PMapNullHistType pmapNullHist; + + // Track emitted diagnostics, and do not repeat them. + llvm::SmallSet emittedDiagnostics; + + /// + /// Pointer Map and Pointer Set + /// --------------------------- + + using PSetType = llvm::SmallSet; + // FIXME: this should be a ScopedHashTable for consistency. + using PMapType = llvm::DenseMap; + + // FIXME: we probably don't need to track it at this level, perhaps + // just tracking at the scope level should be enough? + PMapType *currPmap = nullptr; + PMapType &getPmap() { return *currPmap; } + void markPsetInvalid(mlir::Value ptr, InvalidStyle invalidStyle, + mlir::Location loc, + std::optional extraVal = {}) { + auto &pset = getPmap()[ptr]; + + // If pset is already invalid, don't bother. + if (pset.count(State::getInvalid())) + return; + + // 2.3 - putting invalid into pset(x) is said to invalidate it + pset.insert(State::getInvalid()); + invalidHist[ptr].add(ptr, invalidStyle, loc, extraVal); + } + + void markPsetNull(mlir::Value addr, mlir::Location loc) { + getPmap()[addr].clear(); + getPmap()[addr].insert(State::getNullPtr()); + pmapNullHist[addr] = loc; + } + + void joinPmaps(SmallVectorImpl &pmaps); + + // Provides p1179's 'KILL' functionality. See implementation for more + // information. + void kill(const State &s, InvalidStyle invalidStyle, mlir::Location loc); + void killInPset(mlir::Value ptrKey, const State &s, InvalidStyle invalidStyle, + mlir::Location loc, std::optional extraVal); + + // Local pointers + SmallPtrSet ptrs; + + // Local owners. We use a map instead of a set to track the current generation + // for this owner type internal pointee's. For instance, this allows tracking + // subsequent reuse of owner storage when a non-const use happens. + DenseMap owners; + void addOwner(mlir::Value o) { + assert(!owners.count(o) && "already tracked"); + owners[o] = 0; + } + void incOwner(mlir::Value o) { + assert(owners.count(o) && "entry expected"); + owners[o]++; + } + + // Aggregates and exploded fields. + using ExplodedFieldsTy = llvm::SmallVector; + DenseMap aggregates; + void addAggregate(mlir::Value a, SmallVectorImpl &fields) { + assert(!aggregates.count(a) && "already tracked"); + aggregates[a].swap(fields); + } + + // Useful helpers for debugging + void printPset(PSetType &pset, llvm::raw_ostream &OS = llvm::errs()); + LLVM_DUMP_METHOD void dumpPmap(PMapType &pmap); + LLVM_DUMP_METHOD void dumpCurrentPmap(); + + /// + /// Coroutine tasks (promise_type) + /// ------------------------------ + + // Track types we already know to be a coroutine task (promise_type) + llvm::DenseMap IsTaskTyCache; + // Is the type associated with taskVal a coroutine task? Uses IsTaskTyCache + // or compute it from associated AST node. + bool isTaskType(mlir::Value taskVal); + // Addresses of coroutine Tasks found in the current function. + SmallPtrSet tasks; + + /// + /// Lambdas + /// ------- + + // Track types we already know to be a lambda + llvm::DenseMap IsLambdaTyCache; + // Check if a given cir type is a struct containing a lambda + bool isLambdaType(mlir::Type ty); + // Get the lambda struct from a member access to it. + mlir::Value getLambdaFromMemberAccess(mlir::Value addr); + + /// + /// Scope, context and guards + /// ------------------------- + + // Represents the scope context for IR operations (cir.scope, cir.if, + // then/else regions, etc). Tracks the declaration of variables in the current + // local scope. + struct LexicalScopeContext { + unsigned Depth = 0; + LexicalScopeContext() = delete; + + llvm::PointerUnion parent; + LexicalScopeContext(mlir::Region *R) : parent(R) {} + LexicalScopeContext(mlir::Operation *Op) : parent(Op) {} + ~LexicalScopeContext() = default; + + // Track all local values added in this scope + SmallPtrSet localValues; + + // Track the result of temporaries with coroutine call results, + // they are used to initialize a task. + // + // Value must come directly out of a cir.call to a cir.func which + // is a coroutine. + SmallPtrSet localTempTasks; + + // Track seen lambdas that escape out of the current scope + // (e.g. lambdas returned out of functions). + DenseMap localRetLambdas; + + LLVM_DUMP_METHOD void dumpLocalValues(); + }; + + class LexicalScopeGuard { + LifetimeCheckPass &Pass; + LexicalScopeContext *OldVal = nullptr; + + public: + LexicalScopeGuard(LifetimeCheckPass &p, LexicalScopeContext *L) : Pass(p) { + if (Pass.currScope) { + OldVal = Pass.currScope; + L->Depth++; + } + Pass.currScope = L; + } + + LexicalScopeGuard(const LexicalScopeGuard &) = delete; + LexicalScopeGuard &operator=(const LexicalScopeGuard &) = delete; + LexicalScopeGuard &operator=(LexicalScopeGuard &&other) = delete; + + void cleanup(); + void restore() { Pass.currScope = OldVal; } + ~LexicalScopeGuard() { + cleanup(); + restore(); + } + }; + + class PmapGuard { + LifetimeCheckPass &Pass; + PMapType *OldVal = nullptr; + + public: + PmapGuard(LifetimeCheckPass &lcp, PMapType *L) : Pass(lcp) { + if (Pass.currPmap) { + OldVal = Pass.currPmap; + } + Pass.currPmap = L; + } + + PmapGuard(const PmapGuard &) = delete; + PmapGuard &operator=(const PmapGuard &) = delete; + PmapGuard &operator=(PmapGuard &&other) = delete; + + void restore() { Pass.currPmap = OldVal; } + ~PmapGuard() { restore(); } + }; + + LexicalScopeContext *currScope = nullptr; + + /// + /// AST related + /// ----------- + + std::optional astCtx; + void setASTContext(clang::ASTContext *c) { astCtx = c; } +}; +} // namespace + +static std::string getVarNameFromValue(mlir::Value v) { + + auto srcOp = v.getDefiningOp(); + if (!srcOp) { + auto blockArg = cast(v); + assert(blockArg.getOwner()->isEntryBlock() && "random block args NYI"); + llvm::SmallString<128> finalName; + llvm::raw_svector_ostream Out(finalName); + Out << "fn_arg:" << blockArg.getArgNumber(); + return Out.str().str(); + } + + if (auto allocaOp = dyn_cast(srcOp)) + return allocaOp.getName().str(); + if (auto getElemOp = dyn_cast(srcOp)) { + auto parent = dyn_cast(getElemOp.getAddr().getDefiningOp()); + if (parent) { + llvm::SmallString<128> finalName; + llvm::raw_svector_ostream Out(finalName); + Out << parent.getName() << "." << getElemOp.getName(); + return Out.str().str(); + } + } + if (auto callOp = dyn_cast(srcOp)) { + if (callOp.getCallee()) { + llvm::SmallString<128> finalName; + llvm::raw_svector_ostream Out(finalName); + Out << "call:" << callOp.getCallee()->str(); + return Out.str().str(); + } + } + assert(0 && "how did it get here?"); + return ""; +} + +static Location getEndLoc(Location loc, int idx = 1) { + auto fusedLoc = loc.dyn_cast(); + if (!fusedLoc) + return loc; + return fusedLoc.getLocations()[idx]; +} + +static Location getEndLocForHist(Operation *Op) { + return getEndLoc(Op->getLoc()); +} + +static Location getEndLocIf(IfOp ifOp, Region *R) { + assert(ifOp && "what other regions create their own scope?"); + if (&ifOp.getThenRegion() == R) + return getEndLoc(ifOp.getLoc()); + return getEndLoc(ifOp.getLoc(), /*idx=*/3); +} + +static Location getEndLocForHist(Region *R) { + auto parentOp = R->getParentOp(); + if (isa(parentOp)) + return getEndLocIf(cast(parentOp), R); + if (isa(parentOp)) + return getEndLoc(parentOp->getLoc()); + llvm_unreachable("what other regions create their own scope?"); +} + +static Location getEndLocForHist(LifetimeCheckPass::LexicalScopeContext &lsc) { + assert(!lsc.parent.isNull() && "shouldn't be null"); + if (lsc.parent.is()) + return getEndLocForHist(lsc.parent.get()); + assert(lsc.parent.is() && + "Only support operation beyond this point"); + return getEndLocForHist(lsc.parent.get()); +} + +void LifetimeCheckPass::killInPset(mlir::Value ptrKey, const State &s, + InvalidStyle invalidStyle, + mlir::Location loc, + std::optional extraVal) { + auto &pset = getPmap()[ptrKey]; + if (pset.contains(s)) { + pset.erase(s); + markPsetInvalid(ptrKey, invalidStyle, loc, extraVal); + } +} + +// 2.3 - KILL(x) means to replace all occurrences of x and x' and x'' (etc.) +// in the pmap with invalid. For example, if pmap is {(p1,{a}), (p2,{a'})}, +// KILL(a') would invalidate only p2, and KILL(a) would invalidate both p1 and +// p2. +void LifetimeCheckPass::kill(const State &s, InvalidStyle invalidStyle, + mlir::Location loc) { + assert(s.hasValue() && "does not know how to kill other data types"); + mlir::Value v = s.getData(); + std::optional extraVal; + if (invalidStyle == InvalidStyle::EndOfScope) + extraVal = v; + + for (auto &mapEntry : getPmap()) { + auto ptr = mapEntry.first; + + // We are deleting this entry anyways, nothing to do here. + if (v == ptr) + continue; + + // ... replace all occurrences of x and x' and x''. Start with the primes + // so we first remove uses and then users. + // + // FIXME: add x'', x''', etc... + if (s.isLocalValue() && owners.count(v)) + killInPset(ptr, State::getOwnedBy(v), invalidStyle, loc, extraVal); + killInPset(ptr, s, invalidStyle, loc, extraVal); + } + + // Delete the local value from pmap, since its scope has ended. + if (invalidStyle == InvalidStyle::EndOfScope) { + owners.erase(v); + ptrs.erase(v); + tasks.erase(v); + aggregates.erase(v); + } +} + +void LifetimeCheckPass::LexicalScopeGuard::cleanup() { + auto *localScope = Pass.currScope; + for (auto pointee : localScope->localValues) + Pass.kill(State::getLocalValue(pointee), InvalidStyle::EndOfScope, + getEndLocForHist(*localScope)); + + // Catch interesting dangling references out of returns. + for (auto l : localScope->localRetLambdas) + Pass.checkPointerDeref(l.first, l.second, DerefStyle::RetLambda); +} + +void LifetimeCheckPass::checkBlock(Block &block) { + // Block main role is to hold a list of Operations. + for (Operation &op : block.getOperations()) + checkOperation(&op); +} + +void LifetimeCheckPass::checkRegion(Region ®ion) { + for (Block &block : region.getBlocks()) + checkBlock(block); +} + +void LifetimeCheckPass::checkRegionWithScope(Region ®ion) { + // Add a new scope. Note that as part of the scope cleanup process + // we apply section 2.3 KILL(x) functionality, turning relevant + // references invalid. + LexicalScopeContext lexScope{®ion}; + LexicalScopeGuard scopeGuard{*this, &lexScope}; + for (Block &block : region.getBlocks()) + checkBlock(block); +} + +void LifetimeCheckPass::checkFunc(cir::FuncOp fnOp) { + currFunc = fnOp; + // FIXME: perhaps this should be a function pass, but for now make + // sure we reset the state before looking at other functions. + if (currPmap) + getPmap().clear(); + pmapNullHist.clear(); + invalidHist.clear(); + + // Create a new pmap for this function. + PMapType localPmap{}; + PmapGuard pmapGuard{*this, &localPmap}; + + // Add a new scope. Note that as part of the scope cleanup process + // we apply section 2.3 KILL(x) functionality, turning relevant + // references invalid. + for (Region ®ion : fnOp->getRegions()) + checkRegionWithScope(region); + + // FIXME: store the pmap result for this function, we + // could do some interesting IPA stuff using this info. + currFunc.reset(); +} + +// The join operation between pmap as described in section 2.3. +// +// JOIN({pmap1,...,pmapN}) => +// { (p, pset1(p) U ... U psetN(p) | (p,*) U pmap1 U ... U pmapN }. +// +void LifetimeCheckPass::joinPmaps(SmallVectorImpl &pmaps) { + for (auto &mapEntry : getPmap()) { + auto &val = mapEntry.first; + + PSetType joinPset; + for (auto &pmapOp : pmaps) + llvm::set_union(joinPset, pmapOp[val]); + + getPmap()[val] = joinPset; + } +} + +void LifetimeCheckPass::checkLoop(LoopOp loopOp) { + // 2.4.9. Loops + // + // A loop is treated as if it were the first two loop iterations unrolled + // using an if. For example: + // + // for (/*init*/; /*cond*/; /*incr*/) + // { /*body*/ } + // + // is treated as: + // + // if (/*init*/; /*cond*/) + // { /*body*/; /*incr*/ } + // if (/*cond*/) + // { /*body*/ } + // + // See checkIf for additional explanations. + SmallVector pmapOps; + SmallVector regionsToCheck; + + auto setupLoopRegionsToCheck = [&](bool isSubsequentTaken = false) { + regionsToCheck.clear(); + switch (loopOp.getKind()) { + case LoopOpKind::For: { + regionsToCheck.push_back(&loopOp.getCond()); + regionsToCheck.push_back(&loopOp.getBody()); + if (!isSubsequentTaken) + regionsToCheck.push_back(&loopOp.getStep()); + break; + } + case LoopOpKind::While: { + regionsToCheck.push_back(&loopOp.getCond()); + regionsToCheck.push_back(&loopOp.getBody()); + break; + } + case LoopOpKind::DoWhile: { + // Note this is the reverse order from While above. + regionsToCheck.push_back(&loopOp.getBody()); + regionsToCheck.push_back(&loopOp.getCond()); + break; + } + } + }; + + // From 2.4.9 "Note": + // + // There are only three paths to analyze: + // (1) never taken (the loop body was not entered) + pmapOps.push_back(getPmap()); + + // (2) first taken (the first pass through the loop body, which begins + // with the loop entry pmap) + PMapType loopExitPmap; + { + // Intentional copy from loop entry map + loopExitPmap = getPmap(); + PmapGuard pmapGuard{*this, &loopExitPmap}; + setupLoopRegionsToCheck(); + for (auto *r : regionsToCheck) + checkRegion(*r); + pmapOps.push_back(loopExitPmap); + } + + // (3) and subsequent taken (second or later iteration, which begins with the + // loop body exit pmap and so takes into account any invalidations performed + // in the loop body on any path that could affect the next loop). + // + // This ensures that a subsequent loop iteration does not use a Pointer that + // was invalidated during a previous loop iteration. + // + // Because this analysis gives the same answer for each block of code (always + // converges), all loop iterations after the first get the same answer and + // so we only need to consider the second iteration, and so the analysis + // algorithm remains linear, single-pass. As an optimization, if the loop + // entry pmap is the same as the first loop body exit pmap, there is no need + // to perform the analysis on the second loop iteration; the answer will be + // the same. + if (getPmap() != loopExitPmap) { + // Intentional copy from first taken loop exit pmap + PMapType otherTakenPmap = loopExitPmap; + PmapGuard pmapGuard{*this, &otherTakenPmap}; + setupLoopRegionsToCheck(/*isSubsequentTaken=*/true); + for (auto *r : regionsToCheck) + checkRegion(*r); + pmapOps.push_back(otherTakenPmap); + } + + joinPmaps(pmapOps); +} + +void LifetimeCheckPass::checkAwait(AwaitOp awaitOp) { + // Pretty conservative: assume all regions execute + // sequencially. + // + // FIXME: use branch interface here and only tackle + // the necessary regions. + SmallVector pmapOps; + + for (auto r : awaitOp.getRegions()) { + PMapType regionPmap = getPmap(); + PmapGuard pmapGuard{*this, ®ionPmap}; + checkRegion(*r); + pmapOps.push_back(regionPmap); + } + + joinPmaps(pmapOps); +} + +void LifetimeCheckPass::checkReturn(ReturnOp retOp) { + // Upon return invalidate all local values. Since some return + // values might depend on other local address, check for the + // dangling aspects for this. + if (retOp.getNumOperands() == 0) + return; + + auto retTy = retOp.getOperand(0).getType(); + // FIXME: this can be extended to cover more leaking/dandling + // semantics out of functions. + if (!isLambdaType(retTy)) + return; + + // The return value is loaded from the return slot before + // returning. + auto loadOp = dyn_cast(retOp.getOperand(0).getDefiningOp()); + assert(loadOp && "expected cir.load"); + if (!isa(loadOp.getAddr().getDefiningOp())) + return; + + // Keep track of interesting lambda. + assert(!currScope->localRetLambdas.count(loadOp.getAddr()) && + "lambda already returned?"); + currScope->localRetLambdas.insert( + std::make_pair(loadOp.getAddr(), loadOp.getLoc())); +} + +void LifetimeCheckPass::checkSwitch(SwitchOp switchOp) { + // 2.4.7. A switch(cond) is treated as if it were an equivalent series of + // non-nested if statements with single evaluation of cond; for example: + // + // switch (a) { + // case 1:/*1*/ + // case 2:/*2*/ break; + // default:/*3*/ + // } + // + // is treated as: + // + // if (auto& a=a; a==1) {/*1*/} + // else if (a==1 || a==2) {/*2*/} + // else {/*3*/}. + // + // See checkIf for additional explanations. + SmallVector pmapOps; + + // If there are no regions, pmap is the same. + if (switchOp.getRegions().empty()) + return; + + auto isCaseFallthroughTerminated = [&](Region &r) { + assert(r.getBlocks().size() == 1 && "cannot yet handle branches"); + Block &block = r.back(); + assert(!block.empty() && "case regions cannot be empty"); + + // FIXME: do something special about return terminated? + YieldOp y = dyn_cast(block.back()); + if (!y) + return false; + if (y.isFallthrough()) + return true; + return false; + }; + + auto regions = switchOp.getRegions(); + for (unsigned regionCurrent = 0, regionPastEnd = regions.size(); + regionCurrent != regionPastEnd; ++regionCurrent) { + // Intentional pmap copy, basis to start new path. + PMapType locaCasePmap = getPmap(); + PmapGuard pmapGuard{*this, &locaCasePmap}; + + // At any given point, fallbacks (if not empty) will increase the + // number of control-flow possibilities. For each region ending up + // with a fallback, keep computing the pmap until we hit a region + // that has a non-fallback terminator for the region. + unsigned idx = regionCurrent; + while (idx < regionPastEnd) { + // Note that for 'if' regions we use checkRegionWithScope, since + // there are lexical scopes associated with each region, this is + // not the case for switch's. + checkRegion(regions[idx]); + if (!isCaseFallthroughTerminated(regions[idx])) + break; + idx++; + } + pmapOps.push_back(locaCasePmap); + } + + joinPmaps(pmapOps); +} + +void LifetimeCheckPass::checkIf(IfOp ifOp) { + // Both then and else create their own lexical scopes, take that into account + // while checking then/else. + // + // This is also the moment where pmaps are joined because flow forks: + // pmap(ifOp) = JOIN( pmap(then), pmap(else) ) + // + // To that intent the pmap is copied out before checking each region and + // pmap(ifOp) computed after analysing both paths. + SmallVector pmapOps; + + { + PMapType localThenPmap = getPmap(); + PmapGuard pmapGuard{*this, &localThenPmap}; + checkRegionWithScope(ifOp.getThenRegion()); + pmapOps.push_back(localThenPmap); + } + + // In case there's no 'else' branch, the 'else' pmap is the same as + // prior to the if condition. + if (!ifOp.getElseRegion().empty()) { + PMapType localElsePmap = getPmap(); + PmapGuard pmapGuard{*this, &localElsePmap}; + checkRegionWithScope(ifOp.getElseRegion()); + pmapOps.push_back(localElsePmap); + } else { + pmapOps.push_back(getPmap()); + } + + joinPmaps(pmapOps); +} + +template bool isStructAndHasAttr(mlir::Type ty) { + if (!ty.isa()) + return false; + return hasAttr(ty.cast().getAst()); +} + +static bool isOwnerType(mlir::Type ty) { + // From 2.1: + // + // An Owner uniquely owns another object (cannot dangle). An Owner type is + // expressed using the annotation [[gsl::Owner(DerefType)]] where DerefType is + // the owned type (and (DerefType) may be omitted and deduced as below). For + // example: + // + // template class [[gsl::Owner(T)]] my_unique_smart_pointer; + // + // TODO: The following standard or other types are treated as-if annotated as + // Owners, if not otherwise annotated and if not SharedOwners: + // + // - Every type that satisfies the standard Container requirements and has a + // user-provided destructor. (Example: vector.) DerefType is ::value_type. + // - Every type that provides unary * and has a user-provided destructor. + // (Example: unique_ptr.) DerefType is the ref-unqualified return type of + // operator*. + // - Every type that has a data member or public base class of an Owner type. + // Additionally, for convenient adoption without modifying existing standard + // library headers, the following well known standard types are treated as-if + // annotated as Owners: stack, queue, priority_queue, optional, variant, any, + // and regex. + return isStructAndHasAttr(ty); +} + +static bool containsPointerElts(mlir::cir::StructType s) { + auto members = s.getMembers(); + return std::any_of(members.begin(), members.end(), [](mlir::Type t) { + return t.isa(); + }); +} + +static bool isAggregateType(LifetimeCheckPass *pass, mlir::Type agg) { + auto t = agg.dyn_cast(); + if (!t) + return false; + // Lambdas have their special handling, and shall not be considered as + // aggregate types. + if (pass->isLambdaType(agg)) + return false; + // FIXME: For now we handle this in a more naive way: any pointer + // element we find is enough to consider this an aggregate. But in + // reality it should be as defined in 2.1: + // + // An Aggregate is a type that is not an Indirection and is a class type with + // public data members none of which are references (& or &&) and no + // user-provided copy or move operations, and no base class that is not also + // an Aggregate. The elements of an Aggregate are its public data members. + return containsPointerElts(t); +} + +static bool isPointerType(mlir::Type t) { + // From 2.1: + // + // A Pointer is not an Owner and provides indirect access to an object it does + // not own (can dangle). A Pointer type is expressed using the annotation + // [[gsl::Pointer(DerefType)]] where DerefType is the pointed-to type (and + // (Dereftype) may be omitted and deduced as below). For example: + // + // template class [[gsl::Pointer(T)]] my_span; + // + // TODO: The following standard or other types are treated as-if annotated as + // Pointer, if not otherwise annotated and if not Owners: + // + // - Every type that satisfies the standard Iterator requirements. (Example: + // regex_iterator.) DerefType is the ref-unqualified return type of operator*. + // - Every type that satisfies the Ranges TS Range concept. (Example: + // basic_string_view.) DerefType is the ref-unqualified type of *begin(). + // - Every type that satisfies the following concept. DerefType is the + // ref-unqualified return type of operator*. + // + // template concept + // TriviallyCopyableAndNonOwningAndDereferenceable = + // std::is_trivially_copyable_v && std::is_copy_constructible_v && + // std::is_copy_assignable_v && requires(T t) { *t; }; + // + // - Every closure type of a lambda that captures by reference or captures a + // Pointer by value. DerefType is void. + // - Every type that has a data member or public base class of a Pointer type. + // Additionally, for convenient adoption without modifying existing standard + // library headers, the following well- known standard types are treated as-if + // annotated as Pointers, in addition to raw pointers and references: ref- + // erence_wrapper, and vector::reference. + if (t.isa()) + return true; + return isStructAndHasAttr(t); +} + +void LifetimeCheckPass::classifyAndInitTypeCategories(mlir::Value addr, + mlir::Type t, + mlir::Location loc, + unsigned nestLevel) { + // The same alloca can be hit more than once when checking for dangling + // pointers out of subsequent loop iterations (e.g. second iteraton using + // pointer invalidated in the first run). Since we copy the pmap out to + // start those subsequent checks, make sure sure we skip existing alloca + // tracking. + if (getPmap().count(addr)) + return; + getPmap()[addr] = {}; + + enum TypeCategory { + Unknown = 0, + SharedOwner = 1, + Owner = 1 << 2, + Pointer = 1 << 3, + Indirection = 1 << 4, + Aggregate = 1 << 5, + Value = 1 << 6, + }; + + auto localStyle = [&]() { + if (isPointerType(t)) + return TypeCategory::Pointer; + if (isOwnerType(t)) + return TypeCategory::Owner; + if (isAggregateType(this, t)) + return TypeCategory::Aggregate; + return TypeCategory::Value; + }(); + + switch (localStyle) { + case TypeCategory::Pointer: + // 2.4.2 - When a non-parameter non-member Pointer p is declared, add + // (p, {invalid}) to pmap. + ptrs.insert(addr); + markPsetInvalid(addr, InvalidStyle::NotInitialized, loc); + break; + case TypeCategory::Owner: + // 2.4.2 - When a local Owner x is declared, add (x, {x__1'}) to pmap. + addOwner(addr); + getPmap()[addr].insert(State::getOwnedBy(addr)); + currScope->localValues.insert(addr); + break; + case TypeCategory::Aggregate: { + // 2.1 - Aggregates are types we will “explode” (consider memberwise) at + // local scopes, because the function can operate on the members directly. + + // TODO: only track first level of aggregates subobjects for now, get some + // data before we increase this. + if (nestLevel > 1) + break; + + // Map values for members to it's index in the aggregate. + auto members = t.cast().getMembers(); + SmallVector fieldVals; + fieldVals.assign(members.size(), {}); + + // Go through uses of the alloca via `cir.struct_element_addr`, and + // track only the fields that are actually used. + std::for_each(addr.use_begin(), addr.use_end(), [&](mlir::OpOperand &use) { + auto op = dyn_cast(use.getOwner()); + if (!op) + return; + + auto eltAddr = op.getResult(); + // If nothing is using this GetMemberOp, don't bother since + // it could lead to even more noisy outcomes. + if (eltAddr.use_empty()) + return; + + auto eltTy = + eltAddr.getType().cast().getPointee(); + + // Classify exploded types. Keep alloca original location. + classifyAndInitTypeCategories(eltAddr, eltTy, loc, ++nestLevel); + fieldVals[op.getIndex()] = eltAddr; + }); + + // In case this aggregate gets initialized at once, the fields need + // to be mapped to the elements values. + addAggregate(addr, fieldVals); + + // There might be pointers to this aggregate, so also make a value + // for it. + LLVM_FALLTHROUGH; + } + case TypeCategory::Value: { + // 2.4.2 - When a local Value x is declared, add (x, {x}) to pmap. + getPmap()[addr].insert(State::getLocalValue(addr)); + currScope->localValues.insert(addr); + break; + } + default: + llvm_unreachable("NYI"); + } +} + +void LifetimeCheckPass::checkAlloca(AllocaOp allocaOp) { + classifyAndInitTypeCategories(allocaOp.getAddr(), allocaOp.getAllocaType(), + allocaOp.getLoc(), /*nestLevel=*/0); +} + +void LifetimeCheckPass::checkCoroTaskStore(StoreOp storeOp) { + // Given: + // auto task = [init task]; + // Extend pset(task) such that: + // pset(task) = pset(task) U {any local values used to init task} + auto taskTmp = storeOp.getValue(); + // FIXME: check it's initialization 'init' attr. + auto taskAddr = storeOp.getAddr(); + + // Take the following coroutine creation pattern: + // + // %task = cir.alloca ... + // cir.scope { + // %arg0 = cir.alloca ... + // ... + // %tmp_task = cir.call @corotine_call(%arg0, %arg1, ...) + // cir.store %tmp_task, %task + // ... + // } + // + // Bind values that are coming from alloca's (like %arg0 above) to the + // pset of %task - this effectively leads to some invalidation of %task + // when %arg0 finishes its lifetime at the end of the enclosing cir.scope. + if (auto call = dyn_cast(taskTmp.getDefiningOp())) { + bool potentialTaintedTask = false; + for (auto arg : call.getArgOperands()) { + auto alloca = dyn_cast(arg.getDefiningOp()); + if (alloca && currScope->localValues.count(alloca)) { + getPmap()[taskAddr].insert(State::getLocalValue(alloca)); + potentialTaintedTask = true; + } + } + + // Task are only interesting when there are local addresses leaking + // via the coroutine creation, only track those. + if (potentialTaintedTask) + tasks.insert(taskAddr); + return; + } + llvm_unreachable("expecting cir.call defining op"); +} + +mlir::Value LifetimeCheckPass::getLambdaFromMemberAccess(mlir::Value addr) { + auto op = addr.getDefiningOp(); + // FIXME: we likely want to consider more indirections here... + if (!isa(op)) + return nullptr; + auto allocaOp = + dyn_cast(op->getOperand(0).getDefiningOp()); + if (!allocaOp || !isLambdaType(allocaOp.getAllocaType())) + return nullptr; + return allocaOp; +} + +void LifetimeCheckPass::checkLambdaCaptureStore(StoreOp storeOp) { + auto localByRefAddr = storeOp.getValue(); + auto lambdaCaptureAddr = storeOp.getAddr(); + + if (!isa_and_nonnull(localByRefAddr.getDefiningOp())) + return; + auto lambdaAddr = getLambdaFromMemberAccess(lambdaCaptureAddr); + if (!lambdaAddr) + return; + + if (currScope->localValues.count(localByRefAddr)) + getPmap()[lambdaAddr].insert(State::getLocalValue(localByRefAddr)); +} + +void LifetimeCheckPass::updatePointsToForConstStruct( + mlir::Value addr, mlir::cir::ConstStructAttr value, mlir::Location loc) { + assert(aggregates.count(addr) && "expected association with aggregate"); + int memberIdx = 0; + for (auto &attr : value.getMembers()) { + auto ta = attr.dyn_cast(); + assert(ta && "expected typed attribute"); + auto fieldAddr = aggregates[addr][memberIdx]; + // Unseen fields are not tracked. + if (fieldAddr && ta.getType().isa()) { + assert(ta.isa() && + "other than null not implemented"); + markPsetNull(fieldAddr, loc); + } + memberIdx++; + } +} + +void LifetimeCheckPass::updatePointsToForZeroStruct(mlir::Value addr, + StructType sTy, + mlir::Location loc) { + assert(aggregates.count(addr) && "expected association with aggregate"); + int memberIdx = 0; + for (auto &t : sTy.getMembers()) { + auto fieldAddr = aggregates[addr][memberIdx]; + // Unseen fields are not tracked. + if (fieldAddr && t.isa()) { + markPsetNull(fieldAddr, loc); + } + memberIdx++; + } +} + +static mlir::Operation *ignoreBitcasts(mlir::Operation *op) { + while (auto bitcast = dyn_cast(op)) { + if (bitcast.getKind() != CastKind::bitcast) + return op; + auto b = bitcast.getSrc().getDefiningOp(); + // Do not handle block arguments just yet. + if (!b) + return op; + op = b; + } + return op; +} + +void LifetimeCheckPass::updatePointsTo(mlir::Value addr, mlir::Value data, + mlir::Location loc) { + + auto getArrayFromSubscript = [&](PtrStrideOp strideOp) -> mlir::Value { + auto castOp = dyn_cast(strideOp.getBase().getDefiningOp()); + if (!castOp) + return {}; + if (castOp.getKind() != cir::CastKind::array_to_ptrdecay) + return {}; + return castOp.getSrc(); + }; + + auto dataSrcOp = data.getDefiningOp(); + + // Handle function arguments but not all block arguments just yet. + if (!dataSrcOp) { + auto blockArg = cast(data); + if (!blockArg.getOwner()->isEntryBlock()) + return; + getPmap()[addr].clear(); + getPmap()[addr].insert(State::getLocalValue(data)); + return; + } + + // Ignore chains of bitcasts and update data source. Note that when + // dataSrcOp gets updated, `data` might not be the most updated resource + // to use, so avoid using it directly, and instead get things from newer + // dataSrcOp. + dataSrcOp = ignoreBitcasts(dataSrcOp); + + // 2.4.2 - If the declaration includes an initialization, the + // initialization is treated as a separate operation + if (auto cstOp = dyn_cast(dataSrcOp)) { + // Aggregates can be bulk materialized in CIR, handle proper update of + // individual exploded fields. + if (aggregates.count(addr)) { + if (auto constStruct = + cstOp.getValue().dyn_cast()) { + updatePointsToForConstStruct(addr, constStruct, loc); + return; + } + + if (auto zero = cstOp.getValue().dyn_cast()) { + if (auto zeroStructTy = zero.getType().dyn_cast()) { + updatePointsToForZeroStruct(addr, zeroStructTy, loc); + return; + } + } + return; + } + + assert(cstOp.isNullPtr() && "other than null not implemented"); + assert(getPmap().count(addr) && "address should always be valid"); + // 2.4.2 - If the initialization is default initialization or zero + // initialization, set pset(p) = {null}; for example: + // + // int* p; => pset(p) == {invalid} + // int* p{}; or string_view p; => pset(p) == {null}. + // int *p = nullptr; => pset(p) == {nullptr} => pset(p) == {null} + markPsetNull(addr, loc); + return; + } + + if (auto allocaOp = dyn_cast(dataSrcOp)) { + // p = &x; + getPmap()[addr].clear(); + getPmap()[addr].insert(State::getLocalValue(allocaOp.getAddr())); + return; + } + + if (auto ptrStrideOp = dyn_cast(dataSrcOp)) { + // p = &a[0]; + auto array = getArrayFromSubscript(ptrStrideOp); + if (array) { + getPmap()[addr].clear(); + getPmap()[addr].insert(State::getLocalValue(array)); + } + return; + } + + // Initializes ptr types out of known lib calls marked with pointer + // attributes. TODO: find a better way to tag this. + if (auto callOp = dyn_cast(dataSrcOp)) { + // iter = vector::begin() + getPmap()[addr].clear(); + getPmap()[addr].insert(State::getLocalValue(callOp.getResult(0))); + } + + if (auto loadOp = dyn_cast(dataSrcOp)) { + // handle indirections through a load, a common example are temporaries + // copying the 'this' param to a subsequent call. + updatePointsTo(addr, loadOp.getAddr(), loc); + return; + } + + // What should we add next? +} + +void LifetimeCheckPass::checkStore(StoreOp storeOp) { + auto addr = storeOp.getAddr(); + + // Decompose store's to aggregates into multiple updates to individual fields. + if (aggregates.count(addr)) { + auto data = storeOp.getValue(); + auto dataSrcOp = data.getDefiningOp(); + // Only interested in updating and tracking fields, anything besides + // constants isn't really relevant. + if (dataSrcOp && isa(dataSrcOp)) + updatePointsTo(addr, data, data.getLoc()); + return; + } + + // The bulk of the check is done on top of store to pointer categories, + // which usually represent the most common case. + // + // We handle some special local values, like coroutine tasks and lambdas, + // which could be holding references to things with dangling lifetime. + if (!ptrs.count(addr)) { + if (currScope->localTempTasks.count(storeOp.getValue())) + checkCoroTaskStore(storeOp); + else + checkLambdaCaptureStore(storeOp); + return; + } + + // Only handle ptrs from here on. + updatePointsTo(addr, storeOp.getValue(), storeOp.getValue().getLoc()); +} + +void LifetimeCheckPass::checkLoad(LoadOp loadOp) { + auto addr = loadOp.getAddr(); + // Only interested in checking deference on top of pointer types. + // Note that usually the use of the invalid address happens at the + // load or store using the result of this loadOp. + if (!getPmap().count(addr) || !ptrs.count(addr)) + return; + + if (!loadOp.getIsDeref()) + return; + + checkPointerDeref(addr, loadOp.getLoc()); +} + +void LifetimeCheckPass::emitInvalidHistory(mlir::InFlightDiagnostic &D, + mlir::Value histKey, + mlir::Location warningLoc, + DerefStyle derefStyle) { + assert(invalidHist.count(histKey) && "expected invalid hist"); + auto &hist = invalidHist[histKey]; + unsigned limit = opts.histLimit; + + for (int lastIdx = hist.entries.size() - 1; limit > 0 && lastIdx >= 0; + lastIdx--, limit--) { + auto &info = hist.entries[lastIdx]; + + switch (info.style) { + case InvalidStyle::NotInitialized: { + D.attachNote(info.loc) << "uninitialized here"; + break; + } + case InvalidStyle::EndOfScope: { + if (tasks.count(histKey)) { + StringRef resource = "resource"; + if (auto allocaOp = dyn_cast(info.val->getDefiningOp())) { + if (isLambdaType(allocaOp.getAllocaType())) + resource = "lambda"; + } + D.attachNote((*info.val).getLoc()) + << "coroutine bound to " << resource << " with expired lifetime"; + D.attachNote(info.loc) << "at the end of scope or full-expression"; + } else if (derefStyle == DerefStyle::RetLambda) { + assert(currFunc && "expected function"); + StringRef parent = currFunc->getLambda() ? "lambda" : "function"; + D.attachNote(info.val->getLoc()) + << "declared here but invalid after enclosing " << parent + << " ends"; + } else { + auto outOfScopeVarName = getVarNameFromValue(*info.val); + D.attachNote(info.loc) << "pointee '" << outOfScopeVarName + << "' invalidated at end of scope"; + } + break; + } + case InvalidStyle::NonConstUseOfOwner: { + D.attachNote(info.loc) << "invalidated by non-const use of owner type"; + break; + } + default: + llvm_unreachable("unknown history style"); + } + } +} + +void LifetimeCheckPass::checkPointerDeref(mlir::Value addr, mlir::Location loc, + DerefStyle derefStyle) { + bool hasInvalid = getPmap()[addr].count(State::getInvalid()); + bool hasNullptr = getPmap()[addr].count(State::getNullPtr()); + + auto emitPsetRemark = [&] { + llvm::SmallString<128> psetStr; + llvm::raw_svector_ostream Out(psetStr); + printPset(getPmap()[addr], Out); + emitRemark(loc) << "pset => " << Out.str(); + }; + + // Do not emit the same warning twice or more. + if (emittedDiagnostics.count(loc)) + return; + + bool psetRemarkEmitted = false; + if (opts.emitRemarkPsetAlways()) { + emitPsetRemark(); + psetRemarkEmitted = true; + } + + // 2.4.2 - On every dereference of a Pointer p, enforce that p is valid. + if (!hasInvalid && !hasNullptr) + return; + + // TODO: create verbosity/accuracy levels, for now use deref styles directly + // to decide when not to emit a warning. + + // For indirect calls, do not relly on blunt nullptr passing, require some + // invalidation to have happened in a path. + if (derefStyle == DerefStyle::IndirectCallParam && !hasInvalid) + return; + + // Ok, filtered out questionable warnings, take the bad path leading to this + // deference point and diagnose it. + auto varName = getVarNameFromValue(addr); + auto D = emitWarning(loc); + emittedDiagnostics.insert(loc); + + if (tasks.count(addr)) + D << "use of coroutine '" << varName << "' with dangling reference"; + else if (derefStyle == DerefStyle::RetLambda) + D << "returned lambda captures local variable"; + else if (derefStyle == DerefStyle::CallParam || + derefStyle == DerefStyle::IndirectCallParam) { + bool isAgg = isa_and_nonnull(addr.getDefiningOp()); + D << "passing "; + if (!isAgg) + D << "invalid pointer"; + else + D << "aggregate containing invalid pointer member"; + D << " '" << varName << "'"; + } else + D << "use of invalid pointer '" << varName << "'"; + + // TODO: add accuracy levels, different combinations of invalid and null + // could have different ratios of false positives. + if (hasInvalid && opts.emitHistoryInvalid()) + emitInvalidHistory(D, addr, loc, derefStyle); + + if (hasNullptr && opts.emitHistoryNull()) { + assert(pmapNullHist.count(addr) && "expected nullptr hist"); + auto ¬e = pmapNullHist[addr]; + D.attachNote(*note) << "'nullptr' invalidated here"; + } + + if (!psetRemarkEmitted && opts.emitRemarkPsetInvalid()) + emitPsetRemark(); +} + +static FuncOp getCalleeFromSymbol(ModuleOp mod, StringRef name) { + auto global = mlir::SymbolTable::lookupSymbolIn(mod, name); + assert(global && "expected to find symbol for function"); + return dyn_cast(global); +} + +static const ASTCXXMethodDeclInterface getMethod(ModuleOp mod, CallOp callOp) { + if (!callOp.getCallee()) + return nullptr; + StringRef name = *callOp.getCallee(); + auto method = getCalleeFromSymbol(mod, name); + if (!method || method.getBuiltin()) + return nullptr; + return dyn_cast(method.getAstAttr()); +} + +mlir::Value LifetimeCheckPass::getThisParamPointerCategory(CallOp callOp) { + auto thisptr = callOp.getArgOperand(0); + if (ptrs.count(thisptr)) + return thisptr; + if (auto loadOp = dyn_cast_or_null(thisptr.getDefiningOp())) { + if (ptrs.count(loadOp.getAddr())) + return loadOp.getAddr(); + } + // TODO: add a remark to spot 'this' indirections we currently not track. + return {}; +} + +mlir::Value LifetimeCheckPass::getThisParamOwnerCategory(CallOp callOp) { + auto thisptr = callOp.getArgOperand(0); + if (owners.count(thisptr)) + return thisptr; + if (auto loadOp = dyn_cast_or_null(thisptr.getDefiningOp())) { + if (owners.count(loadOp.getAddr())) + return loadOp.getAddr(); + } + // TODO: add a remark to spot 'this' indirections we currently not track. + return {}; +} + +void LifetimeCheckPass::checkMoveAssignment(CallOp callOp, + ASTCXXMethodDeclInterface m) { + // MyPointer::operator=(MyPointer&&)(%dst, %src) + // or + // MyOwner::operator=(MyOwner&&)(%dst, %src) + auto dst = getThisParamPointerCategory(callOp); + auto src = callOp.getArgOperand(1); + + // Move assignments between pointer categories. + if (dst && ptrs.count(src)) { + // Note that the current pattern here usually comes from a xvalue in src + // where all the initialization is done, and this move assignment is + // where we finally materialize it back to the original pointer category. + getPmap()[dst] = getPmap()[src]; + + // 2.4.2 - It is an error to use a moved-from object. + // To that intent we mark src's pset with invalid. + markPsetInvalid(src, InvalidStyle::MovedFrom, callOp.getLoc()); + return; + } + + // Copy assignments between owner categories. + dst = getThisParamOwnerCategory(callOp); + if (dst && owners.count(src)) { + // Handle as a non const use of owner, invalidating pointers. + checkNonConstUseOfOwner(dst, callOp.getLoc()); + + // 2.4.2 - It is an error to use a moved-from object. + // To that intent we mark src's pset with invalid. + markPsetInvalid(src, InvalidStyle::MovedFrom, callOp.getLoc()); + } +} + +void LifetimeCheckPass::checkCopyAssignment(CallOp callOp, + ASTCXXMethodDeclInterface m) { + // MyIntOwner::operator=(MyIntOwner&)(%dst, %src) + auto dst = getThisParamOwnerCategory(callOp); + auto src = callOp.getArgOperand(1); + + // Copy assignment between owner categories. + if (dst && owners.count(src)) + return checkNonConstUseOfOwner(dst, callOp.getLoc()); + + // Copy assignment between pointer categories. + dst = getThisParamPointerCategory(callOp); + if (dst && ptrs.count(src)) { + getPmap()[dst] = getPmap()[src]; + return; + } +} + +// User defined ctors that initialize from owner types is one +// way of tracking owned pointers. +// +// Example: +// MyIntPointer::MyIntPointer(MyIntOwner const&)(%5, %4) +// +bool LifetimeCheckPass::isCtorInitPointerFromOwner(CallOp callOp) { + if (callOp.getNumArgOperands() < 2) + return false; + + // FIXME: should we scan all arguments past first to look for an owner? + auto ptr = getThisParamPointerCategory(callOp); + auto owner = callOp.getArgOperand(1); + + if (ptr && owners.count(owner)) + return true; + + return false; +} + +void LifetimeCheckPass::checkCtor(CallOp callOp, + ASTCXXConstructorDeclInterface ctor) { + // TODO: zero init + // 2.4.2 if the initialization is default initialization or zero + // initialization, example: + // + // int* p{}; + // string_view p; + // + // both results in pset(p) == {null} + if (ctor.isDefaultConstructor()) { + // First argument passed is always the alloca for the 'this' ptr. + + // Currently two possible actions: + // 1. Skip Owner category initialization. + // 2. Initialize Pointer categories. + auto addr = getThisParamOwnerCategory(callOp); + if (addr) + return; + + addr = getThisParamPointerCategory(callOp); + if (!addr) + return; + + // Not interested in block/function arguments or any indirect + // provided alloca address. + if (!dyn_cast_or_null(addr.getDefiningOp())) + return; + + markPsetNull(addr, callOp.getLoc()); + return; + } + + // User defined copy ctor calls ... + if (ctor.isCopyConstructor()) { + llvm_unreachable("NYI"); + } + + if (isCtorInitPointerFromOwner(callOp)) { + auto addr = getThisParamPointerCategory(callOp); + assert(addr && "expected pointer category"); + auto owner = callOp.getArgOperand(1); + getPmap()[addr].clear(); + getPmap()[addr].insert(State::getOwnedBy(owner)); + return; + } +} + +void LifetimeCheckPass::checkOperators(CallOp callOp, + ASTCXXMethodDeclInterface m) { + auto addr = getThisParamOwnerCategory(callOp); + if (addr) { + // const access to the owner is fine. + if (m.isConst()) + return; + // TODO: this is a place where we can hook in some idiom recocgnition + // so we don't need to use actual source code annotation to make assumptions + // on methods we understand and know to behave nicely. + // + // In P1179, section 2.5.7.12, the use of [[gsl::lifetime_const]] is + // suggested, but it's not part of clang (will it ever?) + return checkNonConstUseOfOwner(addr, callOp.getLoc()); + } + + addr = getThisParamPointerCategory(callOp); + if (addr) { + // The assumption is that method calls on pointer types should trigger + // deref checking. + checkPointerDeref(addr, callOp.getLoc()); + return; + } + + // FIXME: we also need to look at operators from non owner or pointer + // types that could be using Owner/Pointer types as parameters. +} + +mlir::Value +LifetimeCheckPass::getNonConstUseOfOwner(CallOp callOp, + ASTCXXMethodDeclInterface m) { + if (m.isConst()) + return {}; + return getThisParamOwnerCategory(callOp); +} + +void LifetimeCheckPass::checkNonConstUseOfOwner(mlir::Value ownerAddr, + mlir::Location loc) { + // 2.4.2 - On every non-const use of a local Owner o: + // + // - For each entry e in pset(s): Remove e from pset(s), and if no other + // Owner’s pset contains only e, then KILL(e). + kill(State::getOwnedBy(ownerAddr), InvalidStyle::NonConstUseOfOwner, loc); + + // - Set pset(o) = {o__N'}, where N is one higher than the highest + // previously used suffix. For example, initially pset(o) is {o__1'}, on + // o’s first non-const use pset(o) becomes {o__2'}, on o’s second non-const + // use pset(o) becomes {o__3'}, and so on. + incOwner(ownerAddr); + return; +} + +void LifetimeCheckPass::checkForOwnerAndPointerArguments(CallOp callOp, + unsigned firstArgIdx) { + auto numOperands = callOp.getNumArgOperands(); + if (firstArgIdx >= numOperands) + return; + + llvm::SmallSetVector ownersToInvalidate, ptrsToDeref; + for (unsigned i = firstArgIdx, e = numOperands; i != e; ++i) { + auto arg = callOp.getArgOperand(i); + // FIXME: apply p1179 rules as described in 2.5. Very conservative for now: + // + // - Owners: always invalidate. + // - Pointers: always check for deref. + // - Coroutine tasks: check the task for deref when calling methods of + // the task, but also when the passing the task around to other functions. + // - Aggregates: check ptr subelements for deref. + // + // FIXME: even before 2.5 we should only invalidate non-const param types. + if (owners.count(arg)) + ownersToInvalidate.insert(arg); + if (ptrs.count(arg)) + ptrsToDeref.insert(arg); + if (tasks.count(arg)) + ptrsToDeref.insert(arg); + if (aggregates.count(arg)) { + int memberIdx = 0; + auto sTy = + arg.getType().cast().getPointee().dyn_cast(); + assert(sTy && "expected struct type"); + for (auto m : sTy.getMembers()) { + auto ptrMemberAddr = aggregates[arg][memberIdx]; + if (m.isa() && ptrMemberAddr) { + ptrsToDeref.insert(ptrMemberAddr); + } + memberIdx++; + } + } + } + + // FIXME: CIR should track source info on the passed args, so we can get + // accurate location for why the invalidation happens. + for (auto o : ownersToInvalidate) + checkNonConstUseOfOwner(o, callOp.getLoc()); + for (auto p : ptrsToDeref) + checkPointerDeref(p, callOp.getLoc(), + callOp.getCallee() ? DerefStyle::CallParam + : DerefStyle::IndirectCallParam); +} + +void LifetimeCheckPass::checkOtherMethodsAndFunctions( + CallOp callOp, ASTCXXMethodDeclInterface m) { + unsigned firstArgIdx = 0; + + // Looks at a method 'this' pointer: + // - If a method call to a class we consider interesting, like a method + // call on a coroutine task (promise_type). + // - Skip the 'this' for any other method. + if (m && !tasks.count(callOp.getArgOperand(firstArgIdx))) + firstArgIdx++; + checkForOwnerAndPointerArguments(callOp, firstArgIdx); +} + +bool LifetimeCheckPass::isOwnerOrPointerClassMethod( + CallOp callOp, ASTCXXMethodDeclInterface m) { + // For the sake of analysis, these behave like regular functions + if (!m || m.isStatic()) + return false; + // Check the object for owner/pointer by looking at the 'this' pointer. + return getThisParamPointerCategory(callOp) || + getThisParamOwnerCategory(callOp); +} + +bool LifetimeCheckPass::isLambdaType(mlir::Type ty) { + if (IsLambdaTyCache.count(ty)) + return IsLambdaTyCache[ty]; + + IsLambdaTyCache[ty] = false; + auto taskTy = ty.dyn_cast(); + if (!taskTy) + return false; + if (taskTy.getAst().isLambda()) + IsLambdaTyCache[ty] = true; + + return IsLambdaTyCache[ty]; +} + +bool LifetimeCheckPass::isTaskType(mlir::Value taskVal) { + auto ty = taskVal.getType(); + if (IsTaskTyCache.count(ty)) + return IsTaskTyCache[ty]; + + bool result = [&] { + auto taskTy = taskVal.getType().dyn_cast(); + if (!taskTy) + return false; + return taskTy.getAst().hasPromiseType(); + }(); + + IsTaskTyCache[ty] = result; + return result; +} + +void LifetimeCheckPass::trackCallToCoroutine(CallOp callOp) { + if (auto fnName = callOp.getCallee()) { + auto calleeFuncOp = getCalleeFromSymbol(theModule, *fnName); + if (calleeFuncOp && + (calleeFuncOp.getCoroutine() || + (calleeFuncOp.isDeclaration() && callOp->getNumResults() > 0 && + isTaskType(callOp->getResult(0))))) { + currScope->localTempTasks.insert(callOp->getResult(0)); + } + return; + } + // Handle indirect calls to coroutines, for instance when + // lambda coroutines are involved with invokers. + if (callOp->getNumResults() > 0 && isTaskType(callOp->getResult(0))) { + // FIXME: get more guarantees to prevent false positives (perhaps + // apply some tracking analysis before this pass and check for lambda + // idioms). + currScope->localTempTasks.insert(callOp->getResult(0)); + } +} + +void LifetimeCheckPass::checkCall(CallOp callOp) { + if (callOp.getNumArgOperands() == 0) + return; + + // Identify calls to coroutines and track returning temporary task types. + // + // Note that we can't reliably know if a function is a coroutine only as + // part of declaration + trackCallToCoroutine(callOp); + + auto methodDecl = getMethod(theModule, callOp); + if (!isOwnerOrPointerClassMethod(callOp, methodDecl)) + return checkOtherMethodsAndFunctions(callOp, methodDecl); + + // From this point on only owner and pointer class methods handling, + // starting from special methods. + if (auto ctor = dyn_cast(methodDecl)) + return checkCtor(callOp, ctor); + if (methodDecl.isMoveAssignmentOperator()) + return checkMoveAssignment(callOp, methodDecl); + if (methodDecl.isCopyAssignmentOperator()) + return checkCopyAssignment(callOp, methodDecl); + if (methodDecl.isOverloadedOperator()) + return checkOperators(callOp, methodDecl); + + // For any other methods... + + // Non-const member call to a Owner invalidates any of its users. + if (auto owner = getNonConstUseOfOwner(callOp, methodDecl)) { + return checkNonConstUseOfOwner(owner, callOp.getLoc()); + } + + // Take a pset(Ptr) = { Ownr' } where Own got invalidated, this will become + // invalid access to Ptr if any of its methods are used. + auto addr = getThisParamPointerCategory(callOp); + if (addr) + return checkPointerDeref(addr, callOp.getLoc()); +} + +void LifetimeCheckPass::checkOperation(Operation *op) { + if (isa<::mlir::ModuleOp>(op)) { + theModule = cast<::mlir::ModuleOp>(op); + for (Region ®ion : op->getRegions()) + checkRegion(region); + return; + } + + if (isa(op)) { + // Add a new scope. Note that as part of the scope cleanup process + // we apply section 2.3 KILL(x) functionality, turning relevant + // references invalid. + // + // No need to create a new pmap when entering a new scope since it + // doesn't cause control flow to diverge (as it does in presence + // of cir::IfOp or cir::SwitchOp). + // + // Also note that for dangling pointers coming from if init stmts + // should be caught just fine, given that a ScopeOp embraces a IfOp. + LexicalScopeContext lexScope{op}; + LexicalScopeGuard scopeGuard{*this, &lexScope}; + for (Region ®ion : op->getRegions()) + checkRegion(region); + return; + } + + // FIXME: we can do better than sequence of dyn_casts. + if (auto fnOp = dyn_cast(op)) + return checkFunc(fnOp); + if (auto ifOp = dyn_cast(op)) + return checkIf(ifOp); + if (auto switchOp = dyn_cast(op)) + return checkSwitch(switchOp); + if (auto loopOp = dyn_cast(op)) + return checkLoop(loopOp); + if (auto allocaOp = dyn_cast(op)) + return checkAlloca(allocaOp); + if (auto storeOp = dyn_cast(op)) + return checkStore(storeOp); + if (auto loadOp = dyn_cast(op)) + return checkLoad(loadOp); + if (auto callOp = dyn_cast(op)) + return checkCall(callOp); + if (auto awaitOp = dyn_cast(op)) + return checkAwait(awaitOp); + if (auto returnOp = dyn_cast(op)) + return checkReturn(returnOp); +} + +void LifetimeCheckPass::runOnOperation() { + assert(astCtx && "Missing ASTContext, please construct with the right ctor"); + opts.parseOptions(*this); + Operation *op = getOperation(); + checkOperation(op); +} + +std::unique_ptr mlir::createLifetimeCheckPass() { + return std::make_unique(); +} + +std::unique_ptr mlir::createLifetimeCheckPass(clang::ASTContext *astCtx) { + auto lifetime = std::make_unique(); + lifetime->setASTContext(astCtx); + return std::move(lifetime); +} + +std::unique_ptr mlir::createLifetimeCheckPass(ArrayRef remark, + ArrayRef hist, + unsigned hist_limit, + clang::ASTContext *astCtx) { + auto lifetime = std::make_unique(); + lifetime->setASTContext(astCtx); + lifetime->opts.parseOptions(remark, hist, hist_limit); + return std::move(lifetime); +} + +//===----------------------------------------------------------------------===// +// Dump & print helpers +//===----------------------------------------------------------------------===// + +void LifetimeCheckPass::LexicalScopeContext::dumpLocalValues() { + llvm::errs() << "Local values: { "; + for (auto value : localValues) { + llvm::errs() << getVarNameFromValue(value); + llvm::errs() << ", "; + } + llvm::errs() << "}\n"; +} + +void LifetimeCheckPass::State::dump(llvm::raw_ostream &OS, int ownedGen) { + switch (val.getInt()) { + case Invalid: + OS << "invalid"; + break; + case NullPtr: + OS << "nullptr"; + break; + case Global: + OS << "global"; + break; + case LocalValue: + OS << getVarNameFromValue(val.getPointer()); + break; + case OwnedBy: + ownedGen++; // Start from 1. + OS << getVarNameFromValue(val.getPointer()) << "__" << ownedGen << "'"; + break; + default: + llvm_unreachable("Not handled"); + } +} + +void LifetimeCheckPass::printPset(PSetType &pset, llvm::raw_ostream &OS) { + OS << "{ "; + auto size = pset.size(); + for (auto s : pset) { + int ownerGen = 0; + if (s.isOwnedBy()) + ownerGen = owners[s.getData()]; + s.dump(OS, ownerGen); + size--; + if (size > 0) + OS << ", "; + } + OS << " }"; +} + +void LifetimeCheckPass::dumpCurrentPmap() { dumpPmap(*currPmap); } + +void LifetimeCheckPass::dumpPmap(PMapType &pmap) { + llvm::errs() << "pmap {\n"; + int entry = 0; + for (auto &mapEntry : pmap) { + llvm::errs() << " " << entry << ": " << getVarNameFromValue(mapEntry.first) + << " " + << "=> "; + printPset(mapEntry.second); + llvm::errs() << "\n"; + entry++; + } + llvm::errs() << "}\n"; +} diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp new file mode 100644 index 000000000000..8ec0d76226fb --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -0,0 +1,336 @@ +//===- LoweringPrepare.cpp - pareparation work for LLVM lowering ----------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "PassDetail.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/IR/BuiltinAttributes.h" +#include "mlir/IR/Region.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Mangle.h" +#include "clang/Basic/Module.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/Passes.h" +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/Path.h" + +using namespace mlir; +using namespace cir; + +static SmallString<128> getTransformedFileName(ModuleOp theModule) { + SmallString<128> FileName; + + if (theModule.getSymName()) { + FileName = llvm::sys::path::filename(theModule.getSymName()->str()); + } + + if (FileName.empty()) + FileName = ""; + + for (size_t i = 0; i < FileName.size(); ++i) { + // Replace everything that's not [a-zA-Z0-9._] with a _. This set happens + // to be the set of C preprocessing numbers. + if (!clang::isPreprocessingNumberBody(FileName[i])) + FileName[i] = '_'; + } + + return FileName; +} + +/// Return the FuncOp called by `callOp`. +static cir::FuncOp getCalledFunction(cir::CallOp callOp) { + SymbolRefAttr sym = + llvm::dyn_cast_if_present(callOp.getCallableForCallee()); + if (!sym) + return nullptr; + return dyn_cast_or_null( + SymbolTable::lookupNearestSymbolFrom(callOp, sym)); +} + +namespace { +struct LoweringPreparePass : public LoweringPrepareBase { + LoweringPreparePass() = default; + void runOnOperation() override; + + void runOnOp(Operation *op); + void lowerGlobalOp(GlobalOp op); + + /// Build the function that initializes the specified global + cir::FuncOp buildCXXGlobalVarDeclInitFunc(GlobalOp op); + + /// Build a module init function that calls all the dynamic initializers. + void buildCXXGlobalInitFunc(); + + cir::FuncOp + buildRuntimeFunction(mlir::OpBuilder &builder, llvm::StringRef name, + mlir::Location loc, mlir::cir::FuncType type, + mlir::cir::GlobalLinkageKind linkage = + mlir::cir::GlobalLinkageKind::ExternalLinkage); + + cir::GlobalOp + buildRuntimeVariable(mlir::OpBuilder &Builder, llvm::StringRef Name, + mlir::Location Loc, mlir::Type type, + mlir::cir::GlobalLinkageKind Linkage = + mlir::cir::GlobalLinkageKind::ExternalLinkage); + + /// + /// AST related + /// ----------- + + clang::ASTContext *astCtx; + void setASTContext(clang::ASTContext *c) { astCtx = c; } + + /// Tracks current module. + ModuleOp theModule; + + /// Tracks existing dynamic initializers. + llvm::StringMap dynamicInitializerNames; + llvm::SmallVector dynamicInitializers; +}; +} // namespace + +cir::GlobalOp LoweringPreparePass::buildRuntimeVariable( + mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, + mlir::Type type, mlir::cir::GlobalLinkageKind linkage) { + cir::GlobalOp g = + dyn_cast_or_null(SymbolTable::lookupNearestSymbolFrom( + theModule, StringAttr::get(theModule->getContext(), name))); + if (!g) { + g = builder.create(loc, name, type); + g.setLinkageAttr( + mlir::cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage)); + mlir::SymbolTable::setSymbolVisibility( + g, mlir::SymbolTable::Visibility::Private); + } + return g; +} + +cir::FuncOp LoweringPreparePass::buildRuntimeFunction( + mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc, + mlir::cir::FuncType type, mlir::cir::GlobalLinkageKind linkage) { + cir::FuncOp f = + dyn_cast_or_null(SymbolTable::lookupNearestSymbolFrom( + theModule, StringAttr::get(theModule->getContext(), name))); + if (!f) { + f = builder.create(loc, name, type); + f.setLinkageAttr( + mlir::cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage)); + mlir::SymbolTable::setSymbolVisibility( + f, mlir::SymbolTable::Visibility::Private); + mlir::NamedAttrList attrs; + f.setExtraAttrsAttr(mlir::cir::ExtraFuncAttributesAttr::get( + builder.getContext(), attrs.getDictionary(builder.getContext()))); + } + return f; +} + +cir::FuncOp LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(GlobalOp op) { + SmallString<256> fnName; + { + llvm::raw_svector_ostream Out(fnName); + op.getAst()->mangleDynamicInitializer(Out); + // Name numbering + uint32_t cnt = dynamicInitializerNames[fnName]++; + if (cnt) + fnName += "." + llvm::Twine(cnt).str(); + } + + // Create a variable initialization function. + mlir::OpBuilder builder(&getContext()); + builder.setInsertionPointAfter(op); + auto voidTy = ::mlir::cir::VoidType::get(builder.getContext()); + auto fnType = mlir::cir::FuncType::get({}, voidTy); + FuncOp f = + buildRuntimeFunction(builder, fnName, op.getLoc(), fnType, + mlir::cir::GlobalLinkageKind::InternalLinkage); + + // Move over the initialzation code of the ctor region. + auto &block = op.getCtorRegion().front(); + mlir::Block *entryBB = f.addEntryBlock(); + entryBB->getOperations().splice(entryBB->begin(), block.getOperations(), + block.begin(), std::prev(block.end())); + + // Register the destructor call with __cxa_atexit + auto &dtorRegion = op.getDtorRegion(); + if (!dtorRegion.empty()) { + assert(op.getAst() && + op.getAst()->getTLSKind() == clang::VarDecl::TLS_None && " TLS NYI"); + // Create a variable that binds the atexit to this shared object. + builder.setInsertionPointToStart(&theModule.getBodyRegion().front()); + auto Handle = buildRuntimeVariable(builder, "__dso_handle", op.getLoc(), + builder.getI8Type()); + + // Look for the destructor call in dtorBlock + auto &dtorBlock = dtorRegion.front(); + mlir::cir::CallOp dtorCall; + for (auto op : reverse(dtorBlock.getOps())) { + dtorCall = op; + break; + } + assert(dtorCall && "Expected a dtor call"); + cir::FuncOp dtorFunc = getCalledFunction(dtorCall); + assert(dtorFunc && + mlir::isa(*dtorFunc.getAst()) && + "Expected a dtor call"); + + // Create a runtime helper function: + // extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d); + auto voidPtrTy = + ::mlir::cir::PointerType::get(builder.getContext(), voidTy); + auto voidFnTy = mlir::cir::FuncType::get({voidPtrTy}, voidTy); + auto voidFnPtrTy = + ::mlir::cir::PointerType::get(builder.getContext(), voidFnTy); + auto HandlePtrTy = + mlir::cir::PointerType::get(builder.getContext(), Handle.getSymType()); + auto fnAtExitType = mlir::cir::FuncType::get( + {voidFnPtrTy, voidPtrTy, HandlePtrTy}, + mlir::cir::VoidType::get(builder.getContext())); + const char *nameAtExit = "__cxa_atexit"; + FuncOp fnAtExit = + buildRuntimeFunction(builder, nameAtExit, op.getLoc(), fnAtExitType); + + // Replace the dtor call with a call to __cxa_atexit(&dtor, &var, + // &__dso_handle) + builder.setInsertionPointAfter(dtorCall); + mlir::Value args[3]; + auto dtorPtrTy = mlir::cir::PointerType::get(builder.getContext(), + dtorFunc.getFunctionType()); + // dtorPtrTy + args[0] = builder.create( + dtorCall.getLoc(), dtorPtrTy, dtorFunc.getSymName()); + args[0] = builder.create( + dtorCall.getLoc(), voidFnPtrTy, mlir::cir::CastKind::bitcast, args[0]); + args[1] = builder.create(dtorCall.getLoc(), voidPtrTy, + mlir::cir::CastKind::bitcast, + dtorCall.getArgOperand(0)); + args[2] = builder.create( + Handle.getLoc(), HandlePtrTy, Handle.getSymName()); + builder.create(dtorCall.getLoc(), fnAtExit, args); + dtorCall->erase(); + entryBB->getOperations().splice(entryBB->end(), dtorBlock.getOperations(), + dtorBlock.begin(), + std::prev(dtorBlock.end())); + } + + // Replace cir.yield with cir.return + builder.setInsertionPointToEnd(entryBB); + auto &yieldOp = block.getOperations().back(); + assert(isa(yieldOp)); + builder.create(yieldOp.getLoc()); + return f; +} + +void LoweringPreparePass::lowerGlobalOp(GlobalOp op) { + auto &ctorRegion = op.getCtorRegion(); + auto &dtorRegion = op.getDtorRegion(); + + if (!ctorRegion.empty() || !dtorRegion.empty()) { + // Build a variable initialization function and move the initialzation code + // in the ctor region over. + auto f = buildCXXGlobalVarDeclInitFunc(op); + + // Clear the ctor and dtor region + ctorRegion.getBlocks().clear(); + dtorRegion.getBlocks().clear(); + + // Add a function call to the variable initialization function. + assert(!hasAttr( + mlir::cast(*op.getAst())) && + "custom initialization priority NYI"); + dynamicInitializers.push_back(f); + } +} + +void LoweringPreparePass::buildCXXGlobalInitFunc() { + if (dynamicInitializers.empty()) + return; + + SmallVector attrs; + for (auto &f : dynamicInitializers) { + // TODO: handle globals with a user-specified initialzation priority. + auto ctorAttr = mlir::cir::GlobalCtorAttr::get(&getContext(), f.getName()); + attrs.push_back(ctorAttr); + } + + theModule->setAttr("cir.globalCtors", + mlir::ArrayAttr::get(&getContext(), attrs)); + + SmallString<256> fnName; + // Include the filename in the symbol name. Including "sub_" matches gcc + // and makes sure these symbols appear lexicographically behind the symbols + // with priority emitted above. Module implementation units behave the same + // way as a non-modular TU with imports. + // TODO: check CXX20ModuleInits + if (astCtx->getCurrentNamedModule() && + !astCtx->getCurrentNamedModule()->isModuleImplementation()) { + llvm::raw_svector_ostream Out(fnName); + std::unique_ptr MangleCtx( + astCtx->createMangleContext()); + cast(*MangleCtx) + .mangleModuleInitializer(astCtx->getCurrentNamedModule(), Out); + } else { + fnName += "_GLOBAL__sub_I_"; + fnName += getTransformedFileName(theModule); + } + + mlir::OpBuilder builder(&getContext()); + builder.setInsertionPointToEnd(&theModule.getBodyRegion().back()); + auto fnType = mlir::cir::FuncType::get( + {}, mlir::cir::VoidType::get(builder.getContext())); + FuncOp f = + buildRuntimeFunction(builder, fnName, theModule.getLoc(), fnType, + mlir::cir::GlobalLinkageKind::ExternalLinkage); + builder.setInsertionPointToStart(f.addEntryBlock()); + for (auto &f : dynamicInitializers) { + builder.create(f.getLoc(), f); + } + + builder.create(f.getLoc()); +} + +void LoweringPreparePass::runOnOp(Operation *op) { + if (GlobalOp globalOp = cast(op)) { + lowerGlobalOp(globalOp); + return; + } +} + +void LoweringPreparePass::runOnOperation() { + assert(astCtx && "Missing ASTContext, please construct with the right ctor"); + auto *op = getOperation(); + if (isa<::mlir::ModuleOp>(op)) { + theModule = cast<::mlir::ModuleOp>(op); + } + + SmallVector opsToTransform; + op->walk([&](Operation *op) { + if (isa(op)) + opsToTransform.push_back(op); + }); + + for (auto *o : opsToTransform) { + runOnOp(o); + } + + buildCXXGlobalInitFunc(); +} + +std::unique_ptr mlir::createLoweringPreparePass() { + return std::make_unique(); +} + +std::unique_ptr +mlir::createLoweringPreparePass(clang::ASTContext *astCtx) { + auto pass = std::make_unique(); + pass->setASTContext(astCtx); + return std::move(pass); +} diff --git a/clang/lib/CIR/Dialect/Transforms/MergeCleanups.cpp b/clang/lib/CIR/Dialect/Transforms/MergeCleanups.cpp new file mode 100644 index 000000000000..f295361140a9 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/MergeCleanups.cpp @@ -0,0 +1,256 @@ +//===- MergeCleanups.cpp - merge simple return/yield blocks ---------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "PassDetail.h" + +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/Dialect/Passes.h" + +#include "mlir/Dialect/Func/IR/FuncOps.h" + +#include "mlir/IR/Matchers.h" +#include "mlir/IR/PatternMatch.h" +#include "mlir/Support/LogicalResult.h" +#include "mlir/Transforms/GreedyPatternRewriteDriver.h" + +using namespace mlir; +using namespace cir; + +namespace { + +template +struct SimplifyRetYieldBlocks : public mlir::OpRewritePattern { + using OpRewritePattern::OpRewritePattern; + mlir::LogicalResult replaceScopeLikeOp(PatternRewriter &rewriter, + ScopeLikeOpTy scopeLikeOp) const; + + SimplifyRetYieldBlocks(mlir::MLIRContext *context) + : OpRewritePattern(context, /*benefit=*/1) {} + + mlir::LogicalResult + checkAndRewriteRegion(mlir::Region &r, + mlir::PatternRewriter &rewriter) const { + auto &blocks = r.getBlocks(); + + if (blocks.size() <= 1) + return failure(); + + // Rewrite something like this: + // + // cir.if %2 { + // %3 = cir.const(3 : i32) : i32 + // cir.br ^bb1 + // ^bb1: // pred: ^bb0 + // cir.return %3 : i32 + // } + // + // to this: + // + // cir.if %2 { + // %3 = cir.const(3 : i32) : i32 + // cir.return %3 : i32 + // } + // + SmallPtrSet candidateBlocks; + for (Block &block : blocks) { + if (block.isEntryBlock()) + continue; + + auto yieldVars = block.getOps(); + for (cir::YieldOp yield : yieldVars) + candidateBlocks.insert(yield.getOperation()->getBlock()); + + auto retVars = block.getOps(); + for (cir::ReturnOp ret : retVars) + candidateBlocks.insert(ret.getOperation()->getBlock()); + } + + auto changed = mlir::failure(); + for (auto *mergeSource : candidateBlocks) { + if (!(mergeSource->hasNoSuccessors() && mergeSource->hasOneUse())) + continue; + auto *mergeDest = mergeSource->getSinglePredecessor(); + if (!mergeDest || mergeDest->getNumSuccessors() != 1) + continue; + rewriter.eraseOp(mergeDest->getTerminator()); + rewriter.mergeBlocks(mergeSource, mergeDest); + changed = mlir::success(); + } + + return changed; + } + + mlir::LogicalResult + checkAndRewriteLoopCond(mlir::Region &condRegion, + mlir::PatternRewriter &rewriter) const { + SmallVector opsToSimplify; + condRegion.walk([&](Operation *op) { + if (isa(op)) + opsToSimplify.push_back(op); + }); + + // Blocks should only contain one "yield" operation. + auto trivialYield = [&](Block *b) { + if (&b->front() != &b->back()) + return false; + return isa(b->getTerminator()); + }; + + if (opsToSimplify.size() != 1) + return failure(); + BrCondOp brCondOp = cast(opsToSimplify[0]); + + // TODO: leverage SCCP to get improved results. + auto cstOp = dyn_cast(brCondOp.getCond().getDefiningOp()); + if (!cstOp || !cstOp.getValue().isa() || + !trivialYield(brCondOp.getDestTrue()) || + !trivialYield(brCondOp.getDestFalse())) + return failure(); + + // If the condition is constant, no need to use brcond, just yield + // properly, "yield" for false and "yield continue" for true. + auto boolAttr = cstOp.getValue().cast(); + auto *falseBlock = brCondOp.getDestFalse(); + auto *trueBlock = brCondOp.getDestTrue(); + auto *currBlock = brCondOp.getOperation()->getBlock(); + if (boolAttr.getValue()) { + rewriter.eraseOp(opsToSimplify[0]); + rewriter.mergeBlocks(trueBlock, currBlock); + falseBlock->erase(); + } else { + rewriter.eraseOp(opsToSimplify[0]); + rewriter.mergeBlocks(falseBlock, currBlock); + trueBlock->erase(); + } + if (cstOp.use_empty()) + rewriter.eraseOp(cstOp); + return success(); + } + + mlir::LogicalResult + matchAndRewrite(ScopeLikeOpTy op, + mlir::PatternRewriter &rewriter) const override { + return replaceScopeLikeOp(rewriter, op); + } +}; + +// Specialize the template to account for the different build signatures for +// IfOp, ScopeOp, FuncOp, SwitchOp, LoopOp. +template <> +mlir::LogicalResult +SimplifyRetYieldBlocks::replaceScopeLikeOp(PatternRewriter &rewriter, + IfOp ifOp) const { + auto regionChanged = mlir::failure(); + if (checkAndRewriteRegion(ifOp.getThenRegion(), rewriter).succeeded()) + regionChanged = mlir::success(); + if (checkAndRewriteRegion(ifOp.getElseRegion(), rewriter).succeeded()) + regionChanged = mlir::success(); + return regionChanged; +} + +template <> +mlir::LogicalResult +SimplifyRetYieldBlocks::replaceScopeLikeOp(PatternRewriter &rewriter, + ScopeOp scopeOp) const { + // Scope region empty: just remove scope. + if (scopeOp.getRegion().empty()) { + rewriter.eraseOp(scopeOp); + return mlir::success(); + } + + // Scope region non-empty: clean it up. + if (checkAndRewriteRegion(scopeOp.getRegion(), rewriter).succeeded()) + return mlir::success(); + + return mlir::failure(); +} + +template <> +mlir::LogicalResult SimplifyRetYieldBlocks::replaceScopeLikeOp( + PatternRewriter &rewriter, cir::FuncOp funcOp) const { + auto regionChanged = mlir::failure(); + if (checkAndRewriteRegion(funcOp.getRegion(), rewriter).succeeded()) + regionChanged = mlir::success(); + return regionChanged; +} + +template <> +mlir::LogicalResult SimplifyRetYieldBlocks::replaceScopeLikeOp( + PatternRewriter &rewriter, cir::SwitchOp switchOp) const { + auto regionChanged = mlir::failure(); + + // Empty switch statement: just remove it. + if (!switchOp.getCases().has_value() || switchOp.getCases()->empty()) { + rewriter.eraseOp(switchOp); + return mlir::success(); + } + + // Non-empty switch statement: clean it up. + for (auto &r : switchOp.getRegions()) { + if (checkAndRewriteRegion(r, rewriter).succeeded()) + regionChanged = mlir::success(); + } + return regionChanged; +} + +template <> +mlir::LogicalResult SimplifyRetYieldBlocks::replaceScopeLikeOp( + PatternRewriter &rewriter, cir::LoopOp loopOp) const { + auto regionChanged = mlir::failure(); + if (checkAndRewriteRegion(loopOp.getBody(), rewriter).succeeded()) + regionChanged = mlir::success(); + if (checkAndRewriteLoopCond(loopOp.getCond(), rewriter).succeeded()) + regionChanged = mlir::success(); + return regionChanged; +} + +void getMergeCleanupsPatterns(RewritePatternSet &results, + MLIRContext *context) { + results.add, SimplifyRetYieldBlocks, + SimplifyRetYieldBlocks, + SimplifyRetYieldBlocks, + SimplifyRetYieldBlocks>(context); +} + +struct MergeCleanupsPass : public MergeCleanupsBase { + MergeCleanupsPass() = default; + void runOnOperation() override; +}; + +// The same operation rewriting done here could have been performed +// by CanonicalizerPass (adding hasCanonicalizer for target Ops and implementing +// the same from above in CIRDialects.cpp). However, it's currently too +// aggressive for static analysis purposes, since it might remove things where +// a diagnostic can be generated. +// +// FIXME: perhaps we can add one more mode to GreedyRewriteConfig to +// disable this behavior. +void MergeCleanupsPass::runOnOperation() { + auto op = getOperation(); + mlir::RewritePatternSet patterns(&getContext()); + getMergeCleanupsPatterns(patterns, &getContext()); + FrozenRewritePatternSet frozenPatterns(std::move(patterns)); + + SmallVector opsToSimplify; + op->walk([&](Operation *op) { + if (isa( + op)) + opsToSimplify.push_back(op); + }); + + for (auto *o : opsToSimplify) { + bool erase = false; + (void)applyOpPatternsAndFold(o, frozenPatterns, GreedyRewriteConfig(), + &erase); + } +} +} // namespace + +std::unique_ptr mlir::createMergeCleanupsPass() { + return std::make_unique(); +} diff --git a/clang/lib/CIR/Dialect/Transforms/PassDetail.h b/clang/lib/CIR/Dialect/Transforms/PassDetail.h new file mode 100644 index 000000000000..2fdcfbda61e5 --- /dev/null +++ b/clang/lib/CIR/Dialect/Transforms/PassDetail.h @@ -0,0 +1,29 @@ +//===- PassDetail.h - CIR Pass class details --------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef DIALECT_CIR_TRANSFORMS_PASSDETAIL_H_ +#define DIALECT_CIR_TRANSFORMS_PASSDETAIL_H_ + +#include "mlir/IR/Dialect.h" +#include "mlir/Pass/Pass.h" + +namespace mlir { +// Forward declaration from Dialect.h +template +void registerDialect(DialectRegistry ®istry); + +namespace cir { +class CIRDialect; +} // namespace cir + +#define GEN_PASS_CLASSES +#include "clang/CIR/Dialect/Passes.h.inc" + +} // namespace mlir + +#endif // DIALECT_CIR_TRANSFORMS_PASSDETAIL_H_ diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp new file mode 100644 index 000000000000..0b3b459ba002 --- /dev/null +++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp @@ -0,0 +1,449 @@ +//===--- CIRGenAction.cpp - LLVM Code generation Frontend Action ---------===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#include "clang/CIRFrontendAction/CIRGenAction.h" +#include "mlir/Dialect/Func/IR/FuncOps.h" +#include "mlir/Dialect/MemRef/IR/MemRef.h" +#include "mlir/IR/BuiltinOps.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/IR/OperationSupport.h" +#include "mlir/Parser/Parser.h" +#include "clang/AST/ASTConsumer.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclGroup.h" +#include "clang/Basic/DiagnosticFrontend.h" +#include "clang/Basic/FileManager.h" +#include "clang/Basic/LangStandard.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/TargetInfo.h" +#include "clang/CIR/CIRGenerator.h" +#include "clang/CIR/CIRToCIRPasses.h" +#include "clang/CIR/Dialect/IR/CIRDialect.h" +#include "clang/CIR/LowerToLLVM.h" +#include "clang/CIR/Passes.h" +#include "clang/CodeGen/BackendUtil.h" +#include "clang/CodeGen/ModuleBuilder.h" +#include "clang/Driver/DriverDiagnostic.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Frontend/FrontendDiagnostic.h" +#include "clang/Lex/Preprocessor.h" +#include "llvm/Bitcode/BitcodeReader.h" +#include "llvm/IR/DebugInfo.h" +#include "llvm/IR/DiagnosticInfo.h" +#include "llvm/IR/DiagnosticPrinter.h" +#include "llvm/IR/GlobalValue.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/LLVMRemarkStreamer.h" +#include "llvm/IR/Module.h" +#include "llvm/IRReader/IRReader.h" +#include "llvm/LTO/LTOBackend.h" +#include "llvm/Linker/Linker.h" +#include "llvm/Pass.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/Signals.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TimeProfiler.h" +#include "llvm/Support/Timer.h" +#include "llvm/Support/ToolOutputFile.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Transforms/IPO/Internalize.h" + +#include + +using namespace cir; +using namespace clang; + +static std::string sanitizePassOptions(llvm::StringRef o) { + std::string opts{o}; + // MLIR pass options are space separated, but we use ';' in clang since + // space aren't well supported, switch it back. + for (unsigned i = 0, e = opts.size(); i < e; ++i) + if (opts[i] == ';') + opts[i] = ' '; + // If arguments are surrounded with '"', trim them off + return llvm::StringRef(opts).trim('"').str(); +} + +namespace cir { + +static std::unique_ptr lowerFromCIRToLLVMIR( + const clang::FrontendOptions &feOptions, mlir::ModuleOp mlirMod, + std::unique_ptr mlirCtx, llvm::LLVMContext &llvmCtx) { + if (feOptions.ClangIRDirectLowering) + return direct::lowerDirectlyFromCIRToLLVMIR(mlirMod, llvmCtx); + else + return lowerFromCIRToMLIRToLLVMIR(mlirMod, std::move(mlirCtx), llvmCtx); +} + +class CIRGenConsumer : public clang::ASTConsumer { + + virtual void anchor(); + + CIRGenAction::OutputType action; + + DiagnosticsEngine &diagnosticsEngine; + const HeaderSearchOptions &headerSearchOptions; + const CodeGenOptions &codeGenOptions; + const TargetOptions &targetOptions; + const LangOptions &langOptions; + const FrontendOptions &feOptions; + + std::unique_ptr outputStream; + + ASTContext *astContext{nullptr}; + IntrusiveRefCntPtr FS; + std::unique_ptr gen; + +public: + CIRGenConsumer(CIRGenAction::OutputType action, + DiagnosticsEngine &diagnosticsEngine, + IntrusiveRefCntPtr VFS, + const HeaderSearchOptions &headerSearchOptions, + const CodeGenOptions &codeGenOptions, + const TargetOptions &targetOptions, + const LangOptions &langOptions, + const FrontendOptions &feOptions, + std::unique_ptr os) + : action(action), diagnosticsEngine(diagnosticsEngine), + headerSearchOptions(headerSearchOptions), + codeGenOptions(codeGenOptions), targetOptions(targetOptions), + langOptions(langOptions), feOptions(feOptions), + outputStream(std::move(os)), FS(VFS), + gen(std::make_unique(diagnosticsEngine, std::move(VFS), + codeGenOptions)) {} + + void Initialize(ASTContext &ctx) override { + assert(!astContext && "initialized multiple times"); + + astContext = &ctx; + + gen->Initialize(ctx); + } + + bool HandleTopLevelDecl(DeclGroupRef D) override { + PrettyStackTraceDecl CrashInfo(*D.begin(), SourceLocation(), + astContext->getSourceManager(), + "LLVM IR generation of declaration"); + gen->HandleTopLevelDecl(D); + return true; + } + + void HandleCXXStaticMemberVarInstantiation(clang::VarDecl *VD) override { + gen->HandleCXXStaticMemberVarInstantiation(VD); + } + + void HandleInlineFunctionDefinition(FunctionDecl *D) override { + gen->HandleInlineFunctionDefinition(D); + } + + void HandleInterestingDecl(DeclGroupRef D) override { + llvm_unreachable("NYI"); + } + + void HandleTranslationUnit(ASTContext &C) override { + // Note that this method is called after `HandleTopLevelDecl` has already + // ran all over the top level decls. Here clang mostly wraps defered and + // global codegen, followed by running CIR passes. + gen->HandleTranslationUnit(C); + + if (!feOptions.ClangIRDisableCIRVerifier) + if (!gen->verifyModule()) { + llvm::report_fatal_error( + "CIR codegen: module verification error before running CIR passes"); + return; + } + + auto mlirMod = gen->getModule(); + auto mlirCtx = gen->takeContext(); + + auto setupCIRPipelineAndExecute = [&] { + // Sanitize passes options. MLIR uses spaces between pass options + // and since that's hard to fly in clang, we currently use ';'. + std::string lifetimeOpts; + if (feOptions.ClangIRLifetimeCheck) + lifetimeOpts = sanitizePassOptions(feOptions.ClangIRLifetimeCheckOpts); + + // Setup and run CIR pipeline. + bool passOptParsingFailure = false; + if (runCIRToCIRPasses(mlirMod, mlirCtx.get(), C, + !feOptions.ClangIRDisableCIRVerifier, + feOptions.ClangIRLifetimeCheck, lifetimeOpts, + passOptParsingFailure) + .failed()) { + if (passOptParsingFailure) + diagnosticsEngine.Report(diag::err_drv_cir_pass_opt_parsing) + << feOptions.ClangIRLifetimeCheckOpts; + else + llvm::report_fatal_error("CIR codegen: MLIR pass manager fails " + "when running CIR passes!"); + return; + } + }; + + if (!feOptions.ClangIRDisablePasses) { + // Handle source manager properly given that lifetime analysis + // might emit warnings and remarks. + auto &clangSourceMgr = C.getSourceManager(); + FileID MainFileID = clangSourceMgr.getMainFileID(); + + std::unique_ptr FileBuf = + llvm::MemoryBuffer::getMemBuffer( + clangSourceMgr.getBufferOrFake(MainFileID)); + + llvm::SourceMgr mlirSourceMgr; + mlirSourceMgr.AddNewSourceBuffer(std::move(FileBuf), llvm::SMLoc()); + + if (feOptions.ClangIRVerifyDiags) { + mlir::SourceMgrDiagnosticVerifierHandler sourceMgrHandler( + mlirSourceMgr, mlirCtx.get()); + mlirCtx->printOpOnDiagnostic(false); + setupCIRPipelineAndExecute(); + + // Verify the diagnostic handler to make sure that each of the + // diagnostics matched. + if (sourceMgrHandler.verify().failed()) { + // FIXME: we fail ungracefully, there's probably a better way + // to communicate non-zero return so tests can actually fail. + llvm::sys::RunInterruptHandlers(); + exit(1); + } + } else { + mlir::SourceMgrDiagnosticHandler sourceMgrHandler(mlirSourceMgr, + mlirCtx.get()); + setupCIRPipelineAndExecute(); + } + } + + switch (action) { + case CIRGenAction::OutputType::EmitCIR: + if (outputStream && mlirMod) { + // Emit remaining defaulted C++ methods + if (!feOptions.ClangIRDisableEmitCXXDefault) + gen->buildDefaultMethods(); + + // FIXME: we cannot roundtrip prettyForm=true right now. + mlir::OpPrintingFlags flags; + flags.enableDebugInfo(/*enable=*/true, /*prettyForm=*/false); + mlirMod->print(*outputStream, flags); + } + break; + case CIRGenAction::OutputType::EmitMLIR: { + auto loweredMlirModule = lowerFromCIRToMLIR(mlirMod, mlirCtx.get()); + assert(outputStream && "Why are we here without an output stream?"); + // FIXME: we cannot roundtrip prettyForm=true right now. + mlir::OpPrintingFlags flags; + flags.enableDebugInfo(/*enable=*/true, /*prettyForm=*/false); + loweredMlirModule->print(*outputStream, flags); + break; + } + case CIRGenAction::OutputType::EmitLLVM: { + llvm::LLVMContext llvmCtx; + auto llvmModule = + lowerFromCIRToLLVMIR(feOptions, mlirMod, std::move(mlirCtx), llvmCtx); + + llvmModule->setTargetTriple(targetOptions.Triple); + + EmitBackendOutput(diagnosticsEngine, headerSearchOptions, codeGenOptions, + targetOptions, langOptions, + C.getTargetInfo().getDataLayoutString(), + llvmModule.get(), BackendAction::Backend_EmitLL, FS, + std::move(outputStream)); + break; + } + case CIRGenAction::OutputType::EmitObj: { + llvm::LLVMContext llvmCtx; + auto llvmModule = + lowerFromCIRToLLVMIR(feOptions, mlirMod, std::move(mlirCtx), llvmCtx); + + llvmModule->setTargetTriple(targetOptions.Triple); + EmitBackendOutput(diagnosticsEngine, headerSearchOptions, codeGenOptions, + targetOptions, langOptions, + C.getTargetInfo().getDataLayoutString(), + llvmModule.get(), BackendAction::Backend_EmitObj, FS, + std::move(outputStream)); + break; + } + case CIRGenAction::OutputType::EmitAssembly: { + llvm::LLVMContext llvmCtx; + auto llvmModule = + lowerFromCIRToLLVMIR(feOptions, mlirMod, std::move(mlirCtx), llvmCtx); + + llvmModule->setTargetTriple(targetOptions.Triple); + EmitBackendOutput(diagnosticsEngine, headerSearchOptions, codeGenOptions, + targetOptions, langOptions, + C.getTargetInfo().getDataLayoutString(), + llvmModule.get(), BackendAction::Backend_EmitAssembly, + FS, std::move(outputStream)); + break; + } + case CIRGenAction::OutputType::None: + break; + } + } + + void HandleTagDeclDefinition(TagDecl *D) override { + PrettyStackTraceDecl CrashInfo(D, SourceLocation(), + astContext->getSourceManager(), + "CIR generation of declaration"); + gen->HandleTagDeclDefinition(D); + } + + void HandleTagDeclRequiredDefinition(const TagDecl *D) override { + gen->HandleTagDeclRequiredDefinition(D); + } + + void CompleteTentativeDefinition(VarDecl *D) override { + gen->CompleteTentativeDefinition(D); + } + + void CompleteExternalDeclaration(VarDecl *D) override { + llvm_unreachable("NYI"); + } + + void AssignInheritanceModel(CXXRecordDecl *RD) override { + llvm_unreachable("NYI"); + } + + void HandleVTable(CXXRecordDecl *RD) override { gen->HandleVTable(RD); } +}; +} // namespace cir + +void CIRGenConsumer::anchor() {} + +CIRGenAction::CIRGenAction(OutputType act, mlir::MLIRContext *_MLIRContext) + : mlirContext(_MLIRContext ? _MLIRContext : new mlir::MLIRContext), + action(act) {} + +CIRGenAction::~CIRGenAction() { mlirModule.reset(); } + +void CIRGenAction::EndSourceFileAction() { + // If the consumer creation failed, do nothing. + if (!getCompilerInstance().hasASTConsumer()) + return; + + // TODO: pass the module around + // module = cgConsumer->takeModule(); +} + +static std::unique_ptr +getOutputStream(CompilerInstance &ci, StringRef inFile, + CIRGenAction::OutputType action) { + switch (action) { + case CIRGenAction::OutputType::EmitAssembly: + return ci.createDefaultOutputFile(false, inFile, "s"); + case CIRGenAction::OutputType::EmitCIR: + return ci.createDefaultOutputFile(false, inFile, "cir"); + case CIRGenAction::OutputType::EmitMLIR: + return ci.createDefaultOutputFile(false, inFile, "mlir"); + case CIRGenAction::OutputType::EmitLLVM: + return ci.createDefaultOutputFile(false, inFile, "llvm"); + case CIRGenAction::OutputType::EmitObj: + return ci.createDefaultOutputFile(true, inFile, "o"); + case CIRGenAction::OutputType::None: + return nullptr; + } + + llvm_unreachable("Invalid action!"); +} + +std::unique_ptr +CIRGenAction::CreateASTConsumer(CompilerInstance &ci, StringRef inputFile) { + auto out = ci.takeOutputStream(); + if (!out) + out = getOutputStream(ci, inputFile, action); + + auto Result = std::make_unique( + action, ci.getDiagnostics(), &ci.getVirtualFileSystem(), + ci.getHeaderSearchOpts(), ci.getCodeGenOpts(), ci.getTargetOpts(), + ci.getLangOpts(), ci.getFrontendOpts(), std::move(out)); + cgConsumer = Result.get(); + + // Enable generating macro debug info only when debug info is not disabled and + // also macrod ebug info is enabled + if (ci.getCodeGenOpts().getDebugInfo() != llvm::codegenoptions::NoDebugInfo && + ci.getCodeGenOpts().MacroDebugInfo) { + llvm_unreachable("NYI"); + } + + return std::move(Result); +} + +mlir::OwningOpRef +CIRGenAction::loadModule(llvm::MemoryBufferRef mbRef) { + auto module = + mlir::parseSourceString(mbRef.getBuffer(), mlirContext); + assert(module && "Failed to parse ClangIR module"); + return module; +} + +void CIRGenAction::ExecuteAction() { + if (getCurrentFileKind().getLanguage() != Language::CIR) { + this->ASTFrontendAction::ExecuteAction(); + return; + } + + // If this is a CIR file we have to treat it specially. + // TODO: This could be done more logically. This is just modeled at the moment + // mimicing CodeGenAction but this is clearly suboptimal. + auto &ci = getCompilerInstance(); + std::unique_ptr outstream = + getOutputStream(ci, getCurrentFile(), action); + if (action != OutputType::None && !outstream) + return; + + auto &sourceManager = ci.getSourceManager(); + auto fileID = sourceManager.getMainFileID(); + auto mainFile = sourceManager.getBufferOrNone(fileID); + + if (!mainFile) + return; + + mlirContext->getOrLoadDialect(); + mlirContext->getOrLoadDialect(); + mlirContext->getOrLoadDialect(); + + // TODO: unwrap this -- this exists because including the `OwningModuleRef` in + // CIRGenAction's header would require linking the Frontend against MLIR. + // Let's avoid that for now. + auto mlirModule = loadModule(*mainFile); + if (!mlirModule) + return; + + llvm::LLVMContext llvmCtx; + auto llvmModule = lowerFromCIRToLLVMIR( + ci.getFrontendOpts(), mlirModule.release(), + std::unique_ptr(mlirContext), llvmCtx); + + if (outstream) + llvmModule->print(*outstream, nullptr); +} + +void EmitAssemblyAction::anchor() {} +EmitAssemblyAction::EmitAssemblyAction(mlir::MLIRContext *_MLIRContext) + : CIRGenAction(OutputType::EmitAssembly, _MLIRContext) {} + +void EmitCIRAction::anchor() {} +EmitCIRAction::EmitCIRAction(mlir::MLIRContext *_MLIRContext) + : CIRGenAction(OutputType::EmitCIR, _MLIRContext) {} + +void EmitCIROnlyAction::anchor() {} +EmitCIROnlyAction::EmitCIROnlyAction(mlir::MLIRContext *_MLIRContext) + : CIRGenAction(OutputType::None, _MLIRContext) {} + +void EmitMLIRAction::anchor() {} +EmitMLIRAction::EmitMLIRAction(mlir::MLIRContext *_MLIRContext) + : CIRGenAction(OutputType::EmitMLIR, _MLIRContext) {} + +void EmitLLVMAction::anchor() {} +EmitLLVMAction::EmitLLVMAction(mlir::MLIRContext *_MLIRContext) + : CIRGenAction(OutputType::EmitLLVM, _MLIRContext) {} + +void EmitObjAction::anchor() {} +EmitObjAction::EmitObjAction(mlir::MLIRContext *_MLIRContext) + : CIRGenAction(OutputType::EmitObj, _MLIRContext) {} diff --git a/clang/lib/CIR/FrontendAction/CMakeLists.txt b/clang/lib/CIR/FrontendAction/CMakeLists.txt new file mode 100644 index 000000000000..7201db6502e6 --- /dev/null +++ b/clang/lib/CIR/FrontendAction/CMakeLists.txt @@ -0,0 +1,36 @@ +set(LLVM_LINK_COMPONENTS + Core + Support + ) + +get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS) + +add_clang_library(clangCIRFrontendAction + CIRGenAction.cpp + + DEPENDS + MLIRCIROpsIncGen + MLIRCIRASTAttrInterfacesIncGen + MLIRBuiltinLocationAttributesIncGen + MLIRBuiltinTypeInterfacesIncGen + MLIRFunctionInterfacesIncGen + + LINK_LIBS + clangAST + clangBasic + clangCodeGen + clangLex + clangFrontend + clangCIR + clangCIRLoweringDirectToLLVM + clangCIRLoweringThroughMLIR + ${dialect_libs} + MLIRCIR + MLIRAnalysis + MLIRIR + MLIRParser + MLIRSideEffectInterfaces + MLIRTransforms + MLIRSupport + MLIRMemRefDialect + ) diff --git a/clang/lib/CIR/Interfaces/ASTAttrInterfaces.cpp b/clang/lib/CIR/Interfaces/ASTAttrInterfaces.cpp new file mode 100644 index 000000000000..a3f525dd65a3 --- /dev/null +++ b/clang/lib/CIR/Interfaces/ASTAttrInterfaces.cpp @@ -0,0 +1,15 @@ +//====- ASTAttrInterfaces.cpp - Interface to AST Attributes ---------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +#include "clang/CIR/Interfaces/ASTAttrInterfaces.h" + +#include "llvm/ADT/SmallVector.h" + +using namespace mlir::cir; + +/// Include the generated type qualifiers interfaces. +#include "clang/CIR/Interfaces/ASTAttrInterfaces.cpp.inc" diff --git a/clang/lib/CIR/Interfaces/CMakeLists.txt b/clang/lib/CIR/Interfaces/CMakeLists.txt new file mode 100644 index 000000000000..3f41389807d7 --- /dev/null +++ b/clang/lib/CIR/Interfaces/CMakeLists.txt @@ -0,0 +1,14 @@ +add_clang_library(MLIRCIRASTAttrInterfaces + ASTAttrInterfaces.cpp + + ADDITIONAL_HEADER_DIRS + ${MLIR_MAIN_INCLUDE_DIR}/mlir/Interfaces + + DEPENDS + MLIRCIRASTAttrInterfacesIncGen + + LINK_LIBS + ${dialect_libs} + MLIRIR + MLIRSupport + ) diff --git a/clang/lib/CMakeLists.txt b/clang/lib/CMakeLists.txt index 1526d65795f8..7163923ca27b 100644 --- a/clang/lib/CMakeLists.txt +++ b/clang/lib/CMakeLists.txt @@ -30,3 +30,4 @@ if(CLANG_INCLUDE_TESTS) endif() add_subdirectory(Interpreter) add_subdirectory(Support) +add_subdirectory(CIR) diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp index c2c5adfa03e8..11abfbc8fedc 100644 --- a/clang/lib/Driver/ToolChains/Clang.cpp +++ b/clang/lib/Driver/ToolChains/Clang.cpp @@ -4599,6 +4599,16 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA, } } + if (Args.hasArg(options::OPT_fclangir_enable) || + Args.hasArg(options::OPT_emit_cir)) + CmdArgs.push_back("-fclangir-enable"); + + if (Args.hasArg(options::OPT_fclangir_direct_lowering)) + CmdArgs.push_back("-fclangir-direct-lowering"); + + if (Args.hasArg(options::OPT_clangir_disable_passes)) + CmdArgs.push_back("-clangir-disable-passes"); + if (IsOpenMPDevice) { // We have to pass the triple of the host if compiling for an OpenMP device. std::string NormalizedTriple = diff --git a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp index f81b5d0e4830..16ea9dbb3ae2 100644 --- a/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp +++ b/clang/lib/ExtractAPI/Serialization/SymbolGraphSerializer.cpp @@ -186,6 +186,7 @@ StringRef getLanguageName(Language Lang) { case Language::Unknown: case Language::Asm: case Language::LLVM_IR: + case Language::CIR: llvm_unreachable("Unsupported language kind"); } diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index f590b351f63a..a7f45d77b17a 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -2459,6 +2459,9 @@ static const auto &getFrontendActionTable() { {frontend::DumpTokens, OPT_dump_tokens}, {frontend::EmitAssembly, OPT_S}, {frontend::EmitBC, OPT_emit_llvm_bc}, + {frontend::EmitCIR, OPT_emit_cir}, + {frontend::EmitCIROnly, OPT_emit_cir_only}, + {frontend::EmitMLIR, OPT_emit_mlir}, {frontend::EmitHTML, OPT_emit_html}, {frontend::EmitLLVM, OPT_emit_llvm}, {frontend::EmitLLVMOnly, OPT_emit_llvm_only}, @@ -2819,6 +2822,30 @@ static bool ParseFrontendArgs(FrontendOptions &Opts, ArgList &Args, Diags.Report(diag::err_drv_argument_only_allowed_with) << "-fsystem-module" << "-emit-module"; + if (Args.hasArg(OPT_fclangir_enable) || Args.hasArg(OPT_emit_cir)) + Opts.UseClangIRPipeline = true; + + if (Args.hasArg(OPT_fclangir_direct_lowering)) + Opts.ClangIRDirectLowering = true; + + if (Args.hasArg(OPT_clangir_disable_passes)) + Opts.ClangIRDisablePasses = true; + + if (Args.hasArg(OPT_clangir_disable_verifier)) + Opts.ClangIRDisableCIRVerifier = true; + + if (Args.hasArg(OPT_clangir_disable_emit_cxx_default)) + Opts.ClangIRDisableEmitCXXDefault = true; + + if (Args.hasArg(OPT_clangir_verify_diagnostics)) + Opts.ClangIRVerifyDiags = true; + + if (const Arg *A = Args.getLastArg(OPT_fclangir_lifetime_check, + OPT_fclangir_lifetime_check_EQ)) { + Opts.ClangIRLifetimeCheck = true; + Opts.ClangIRLifetimeCheckOpts = A->getValue(); + } + if (Args.hasArg(OPT_aux_target_cpu)) Opts.AuxTargetCPU = std::string(Args.getLastArgValue(OPT_aux_target_cpu)); if (Args.hasArg(OPT_aux_target_feature)) @@ -4138,6 +4165,9 @@ static bool isStrictlyPreprocessorAction(frontend::ActionKind Action) { case frontend::ASTView: case frontend::EmitAssembly: case frontend::EmitBC: + case frontend::EmitCIR: + case frontend::EmitCIROnly: + case frontend::EmitMLIR: case frontend::EmitHTML: case frontend::EmitLLVM: case frontend::EmitLLVMOnly: diff --git a/clang/lib/Frontend/FrontendAction.cpp b/clang/lib/Frontend/FrontendAction.cpp index 53cb48d2de9e..5ac4a136e67c 100644 --- a/clang/lib/Frontend/FrontendAction.cpp +++ b/clang/lib/Frontend/FrontendAction.cpp @@ -752,6 +752,26 @@ bool FrontendAction::BeginSourceFile(CompilerInstance &CI, return true; } + // TODO: blindly duplicating for now + if (Input.getKind().getLanguage() == Language::CIR) { + assert(hasCIRSupport() && "This action does not have CIR file support!"); + + // Inform the diagnostic client we are processing a source file. + CI.getDiagnosticClient().BeginSourceFile(CI.getLangOpts(), nullptr); + HasBegunSourceFile = true; + + // Initialize the action. + if (!BeginSourceFileAction(CI)) + return false; + + // Initialize the main file entry. + if (!CI.InitializeSourceManager(CurrentInput)) + return false; + + FailureCleanup.release(); + return true; + } + // If the implicit PCH include is actually a directory, rather than // a single file, search for a suitable PCH file in that directory. if (!CI.getPreprocessorOpts().ImplicitPCHInclude.empty()) { diff --git a/clang/lib/FrontendTool/CMakeLists.txt b/clang/lib/FrontendTool/CMakeLists.txt index 51c379ade270..323d2afd0bdd 100644 --- a/clang/lib/FrontendTool/CMakeLists.txt +++ b/clang/lib/FrontendTool/CMakeLists.txt @@ -12,6 +12,20 @@ set(link_libs clangRewriteFrontend ) +list(APPEND link_libs + clangCIRFrontendAction + MLIRCIRTransforms + MLIRIR + MLIRPass + ) +list(APPEND deps + MLIRBuiltinLocationAttributesIncGen + MLIRBuiltinTypeInterfacesIncGen + ) + +include_directories(${LLVM_MAIN_SRC_DIR}/../mlir/include) +include_directories(${CMAKE_BINARY_DIR}/tools/mlir/include) + if(CLANG_ENABLE_ARCMT) list(APPEND link_libs clangARCMigrate diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index c77e0d916e28..dac9923afdca 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -31,6 +31,11 @@ #include "llvm/Support/BuryPointer.h" #include "llvm/Support/DynamicLibrary.h" #include "llvm/Support/ErrorHandling.h" +#include "mlir/IR/AsmState.h" +#include "mlir/IR/MLIRContext.h" +#include "mlir/Pass/PassManager.h" +#include "clang/CIR/Dialect/Passes.h" +#include "clang/CIRFrontendAction/CIRGenAction.h" using namespace clang; using namespace llvm::opt; @@ -42,6 +47,26 @@ CreateFrontendBaseAction(CompilerInstance &CI) { StringRef Action("unknown"); (void)Action; + auto UseCIR = CI.getFrontendOpts().UseClangIRPipeline; + auto Act = CI.getFrontendOpts().ProgramAction; + + auto EmitsCIR = Act == EmitCIR || Act == EmitCIROnly; + auto IsImplementedCIROutput = EmitsCIR || Act == EmitLLVM || + Act == EmitMLIR || Act == EmitAssembly || + Act == EmitObj; + + if (UseCIR && !IsImplementedCIROutput) + llvm::report_fatal_error("-fclangir-enable currently only works with " + "-emit-cir, -emit-cir-only, -emit-mlir, " + "-emit-llvm, -emit-obj, and -S"); + if (!UseCIR && EmitsCIR) + llvm::report_fatal_error( + "-emit-cir and -emit-cir-only only valid when using -fclangir-enable"); + + if (CI.getFrontendOpts().ClangIRDirectLowering && Act == EmitMLIR) + llvm::report_fatal_error( + "ClangIR direct lowering is incompatible with -emit-mlir"); + switch (CI.getFrontendOpts().ProgramAction) { case ASTDeclList: return std::make_unique(); case ASTDump: return std::make_unique(); @@ -51,13 +76,25 @@ CreateFrontendBaseAction(CompilerInstance &CI) { return std::make_unique(); case DumpRawTokens: return std::make_unique(); case DumpTokens: return std::make_unique(); - case EmitAssembly: return std::make_unique(); + case EmitAssembly: + if (UseCIR) + return std::make_unique<::cir::EmitAssemblyAction>(); + return std::make_unique(); case EmitBC: return std::make_unique(); + case EmitCIR: return std::make_unique<::cir::EmitCIRAction>(); + case EmitCIROnly: return std::make_unique<::cir::EmitCIROnlyAction>(); + case EmitMLIR: return std::make_unique<::cir::EmitMLIRAction>(); case EmitHTML: return std::make_unique(); - case EmitLLVM: return std::make_unique(); + case EmitLLVM: + if (UseCIR) + return std::make_unique<::cir::EmitLLVMAction>(); + return std::make_unique(); case EmitLLVMOnly: return std::make_unique(); case EmitCodeGenOnly: return std::make_unique(); - case EmitObj: return std::make_unique(); + case EmitObj: + if (UseCIR) + return std::make_unique<::cir::EmitObjAction>(); + return std::make_unique(); case ExtractAPI: return std::make_unique(); case FixIt: return std::make_unique(); @@ -260,6 +297,19 @@ bool ExecuteCompilerInvocation(CompilerInstance *Clang) { } #endif + if (!Clang->getFrontendOpts().MLIRArgs.empty()) { + mlir::registerCIRPasses(); + mlir::registerMLIRContextCLOptions(); + mlir::registerPassManagerCLOptions(); + mlir::registerAsmPrinterCLOptions(); + unsigned NumArgs = Clang->getFrontendOpts().MLIRArgs.size(); + auto Args = std::make_unique(NumArgs + 2); + Args[0] = "ClangIR (MLIR option parsing)"; + for (unsigned i = 0; i != NumArgs; ++i) + Args[i + 1] = Clang->getFrontendOpts().MLIRArgs[i].c_str(); + Args[NumArgs + 1] = nullptr; + llvm::cl::ParseCommandLineOptions(NumArgs + 1, Args.get()); + } // If there were errors in processing arguments, don't do anything else. if (Clang->getDiagnostics().hasErrorOccurred()) return false; diff --git a/clang/tools/CMakeLists.txt b/clang/tools/CMakeLists.txt index 90ef04164979..e68138b25af8 100644 --- a/clang/tools/CMakeLists.txt +++ b/clang/tools/CMakeLists.txt @@ -3,6 +3,9 @@ create_subdirectory_options(CLANG TOOL) add_clang_subdirectory(diagtool) add_clang_subdirectory(driver) add_clang_subdirectory(apinotes-test) +add_clang_subdirectory(cir-opt) +add_clang_subdirectory(cir-translate) +add_clang_subdirectory(cir-lsp-server) add_clang_subdirectory(clang-diff) add_clang_subdirectory(clang-format) add_clang_subdirectory(clang-format-vs) diff --git a/llvm/CMakeLists.txt b/llvm/CMakeLists.txt index 3d6f5e6f9d3d..fb7d8eb7c6d7 100644 --- a/llvm/CMakeLists.txt +++ b/llvm/CMakeLists.txt @@ -58,7 +58,7 @@ project(LLVM # Must go after project(..) include(GNUInstallDirs) -set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard to conform to") +set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to conform to") set(CMAKE_CXX_STANDARD_REQUIRED YES) if (CYGWIN) # Cygwin is a bit stricter and lack things like 'strdup', 'stricmp', etc in @@ -84,7 +84,7 @@ endif() # LLVM_EXTERNAL_${project}_SOURCE_DIR using LLVM_ALL_PROJECTS # This allows an easy way of setting up a build directory for llvm and another # one for llvm+clang+... using the same sources. -set(LLVM_ALL_PROJECTS "bolt;clang;clang-tools-extra;compiler-rt;cross-project-tests;libc;libclc;libcxx;libcxxabi;libunwind;lld;lldb;mlir;openmp;polly;pstl") +set(LLVM_ALL_PROJECTS "bolt;cir;clang;clang-tools-extra;compiler-rt;cross-project-tests;libc;libclc;libcxx;libcxxabi;libunwind;lld;lldb;mlir;openmp;polly;pstl") # The flang project is not yet part of "all" projects (see C++ requirements) set(LLVM_EXTRA_PROJECTS "flang") # List of all known projects in the mono repo @@ -118,6 +118,17 @@ if ("flang" IN_LIST LLVM_ENABLE_PROJECTS) endif() endif() +if ("cir" IN_LIST LLVM_ENABLE_PROJECTS) + if (NOT "mlir" IN_LIST LLVM_ENABLE_PROJECTS) + message(STATUS "Enabling MLIR as a dependency to CIR") + list(APPEND LLVM_ENABLE_PROJECTS "mlir") + endif() + + if (NOT "clang" IN_LIST LLVM_ENABLE_PROJECTS) + message(FATAL_ERROR "Clang is not enabled, but is required to use CIR") + endif() +endif() + # LLVM_ENABLE_PROJECTS_USED is `ON` if the user has ever used the # `LLVM_ENABLE_PROJECTS` CMake cache variable. This exists for # several reasons: @@ -142,6 +153,13 @@ if (LLVM_ENABLE_PROJECTS_USED OR NOT LLVM_ENABLE_PROJECTS STREQUAL "") string(REGEX REPLACE "-" "_" upper_proj ${upper_proj}) if ("${proj}" IN_LIST LLVM_ENABLE_PROJECTS) message(STATUS "${proj} project is enabled") + # ClangIR is integrated inside clang and also provides the cir-opt, + # it needs some special handling. + if ("${proj}" STREQUAL "cir") + set(CLANG_ENABLE_CIR ON) + continue() + endif() + set(SHOULD_ENABLE_PROJECT TRUE) set(PROJ_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../${proj}") if(NOT EXISTS "${PROJ_DIR}" OR NOT IS_DIRECTORY "${PROJ_DIR}") diff --git a/llvm/tools/CMakeLists.txt b/llvm/tools/CMakeLists.txt index c6116ac81d12..ef4ac1eff6eb 100644 --- a/llvm/tools/CMakeLists.txt +++ b/llvm/tools/CMakeLists.txt @@ -37,11 +37,10 @@ add_llvm_tool_subdirectory(llvm-profdata) # Projects supported via LLVM_EXTERNAL_*_SOURCE_DIR need to be explicitly # specified. +add_llvm_external_project(mlir) add_llvm_external_project(clang) add_llvm_external_project(lld) add_llvm_external_project(lldb) -add_llvm_external_project(mlir) -# Flang depends on mlir, so place it afterward add_llvm_external_project(flang) add_llvm_external_project(bolt) diff --git a/mlir/CMakeLists.txt b/mlir/CMakeLists.txt index 088aec88fff9..ac98cc290a0b 100644 --- a/mlir/CMakeLists.txt +++ b/mlir/CMakeLists.txt @@ -162,6 +162,11 @@ add_subdirectory(tools/mlir-tblgen) add_subdirectory(tools/mlir-linalg-ods-gen) add_subdirectory(tools/mlir-pdll) +set(MLIR_TABLEGEN_EXE "${MLIR_TABLEGEN_EXE}" CACHE INTERNAL "") +set(MLIR_TABLEGEN_TARGET "${MLIR_TABLEGEN_TARGET}" CACHE INTERNAL "") +set(MLIR_PDLL_TABLEGEN_EXE "${MLIR_PDLL_TABLEGEN_EXE}" CACHE INTERNAL "") +set(MLIR_PDLL_TABLEGEN_TARGET "${MLIR_PDLL_TABLEGEN_TARGET}" CACHE INTERNAL "") + add_subdirectory(include/mlir) add_subdirectory(lib) # C API needs all dialects for registration, but should be built before tests. diff --git a/mlir/include/mlir/IR/BuiltinAttributeInterfaces.td b/mlir/include/mlir/IR/BuiltinAttributeInterfaces.td index 45295e874f3b..63556ed4a95d 100644 --- a/mlir/include/mlir/IR/BuiltinAttributeInterfaces.td +++ b/mlir/include/mlir/IR/BuiltinAttributeInterfaces.td @@ -15,6 +15,25 @@ include "mlir/IR/OpBase.td" +//===----------------------------------------------------------------------===// +// TypedAttrInterface +//===----------------------------------------------------------------------===// + +def TypedAttrInterface : AttrInterface<"TypedAttr"> { + let cppNamespace = "::mlir"; + + let description = [{ + This interface is used for attributes that have a type. The type of an + attribute is understood to represent the type of the data contained in the + attribute and is often used as the type of a value with this data. + }]; + + let methods = [InterfaceMethod< + "Get the attribute's type", + "::mlir::Type", "getType" + >]; +} + //===----------------------------------------------------------------------===// // ElementsAttrInterface //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td index cd581fce0b34..6eaa52effb08 100644 --- a/mlir/include/mlir/IR/OpBase.td +++ b/mlir/include/mlir/IR/OpBase.td @@ -1944,7 +1944,7 @@ class StaticInterfaceMethod; // Interface represents a base interface. -class Interface { +/*class Interface { // A human-readable description of what this interface does. string description = ""; @@ -1967,14 +1967,60 @@ class Interface { // An optional code block containing extra declarations to place in both // the interface and trait declaration. code extraSharedClassDeclaration = ""; +}*/ +class Interface baseInterfacesArg = []> { + // A human-readable description of what this interface does. + string description = ""; + + // The name given to the c++ interface class. + string cppInterfaceName = name; + + // The C++ namespace that this interface should be placed into. + // + // To specify nested namespaces, use "::" as the delimiter, e.g., given + // "A::B", ops will be placed in `namespace A { namespace B { } }`. + string cppNamespace = ""; + + // The list of methods defined by this interface. + list methods = []; + + // An optional code block containing extra declarations to place in the + // interface declaration. + code extraClassDeclaration = ""; + + // An optional code block containing extra declarations to place in both + // the interface and trait declaration. + code extraSharedClassDeclaration = ""; + + // An optional code block for adding additional "classof" logic. This can + // be used to better enable "optional" interfaces, where an entity only + // implements the interface if some dynamic characteristic holds. + // `$_attr`/`$_op`/`$_type` may be used to refer to an instance of the + // interface instance being checked. + code extraClassOf = ""; + + // An optional set of base interfaces that this interface + // "derives" from. + list baseInterfaces = baseInterfacesArg; } // AttrInterface represents an interface registered to an attribute. -class AttrInterface : Interface, InterfaceTrait, - Attr()">, - name # " instance"> -{ +//class AttrInterface : Interface, InterfaceTrait, +// Attr()">, +// name # " instance"> +//{ +// let storageType = !if(!empty(cppNamespace), "", cppNamespace # "::") # name; +// let returnType = storageType; +// let convertFromStorage = "$_self"; +//} +// AttrInterface represents an interface registered to an attribute. +class AttrInterface baseInterfaces = []> + : Interface, InterfaceTrait, + Attr($_self)">, + name # " instance" + > { let storageType = !if(!empty(cppNamespace), "", cppNamespace # "::") # name; let returnType = storageType; let convertFromStorage = "$_self"; @@ -2402,4 +2448,7 @@ class TCopVTEtAreSameAt indices> : CPred< "[this](unsigned i) { return getElementTypeOrSelf(this->getOperand(i)); " "}))">; +def DictArrayAttr : + TypedArrayAttrBase; + #endif // OP_BASE diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h index f9cd96e9762e..54c0c2131291 100644 --- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.h +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.h @@ -194,6 +194,29 @@ private: }; } // namespace SideEffects +namespace Speculation { +/// This enum is returned from the `getSpeculatability` method in the +/// `ConditionallySpeculatable` op interface. +enum class Speculatability { + /// The Operation in question cannot be speculatively executed. This could be + /// because it may invoke undefined behavior or have other side effects. + NotSpeculatable, + + // The Operation in question can be speculatively executed. It does not have + // any side effects or undefined behavior. + Speculatable, + + // The Operation in question can be speculatively executed if all the + // operations in all attached regions can also be speculatively executed. + RecursivelySpeculatable, +}; + +constexpr auto NotSpeculatable = Speculatability::NotSpeculatable; +constexpr auto Speculatable = Speculatability::Speculatable; +constexpr auto RecursivelySpeculatable = + Speculatability::RecursivelySpeculatable; +} // namespace Speculation + //===----------------------------------------------------------------------===// // SideEffect Traits //===----------------------------------------------------------------------===// diff --git a/mlir/include/mlir/Interfaces/SideEffectInterfaces.td b/mlir/include/mlir/Interfaces/SideEffectInterfaces.td index 1c12a5a56e7f..84f94a2ff511 100644 --- a/mlir/include/mlir/Interfaces/SideEffectInterfaces.td +++ b/mlir/include/mlir/Interfaces/SideEffectInterfaces.td @@ -82,4 +82,56 @@ def NoSideEffect : MemoryEffects<[]>; // Op has recursively computed side effects. def RecursiveSideEffects : NativeOpTrait<"HasRecursiveSideEffects">; +// Op has no effect on memory but may have undefined behavior. +def NoMemoryEffect : MemoryEffects<[]>; + +//===----------------------------------------------------------------------===// +// Speculation +//===----------------------------------------------------------------------===// + +// Used to inject an implementation of getSpeculatability. Users should not use +// this directly. +def RecursivelySpeculatableImplTrait + : NativeOpTrait<"RecursivelySpeculatableImplTrait">; + +// Used to inject an implementation of getSpeculatability. Users should not use +// this directly. +def AlwaysSpeculatableImplTrait + : NativeOpTrait<"AlwaysSpeculatableImplTrait">; + +// This op interface enables Op authors to inject custom logic to determine +// whether an Operation can be speculatively executed. Ops that implement this +// interface need to implement the custom logic in the `getSpeculatability` method. +// For instance, the `getSpeculatability` for a specific op may check the attributes +// or input types to determine whether that specific Operation is speculatable. +def ConditionallySpeculatable : OpInterface<"ConditionallySpeculatable"> { + let description = [{ + An interface used to query information about the speculability of an + operation. + }]; + let cppNamespace = "::mlir"; + + let methods = [ + InterfaceMethod<[{ + Returns value indicating whether the specific operation in question can + be speculatively executed. Please see the documentation on the + Speculatability enum to know how to interpret the return value. + }], + "::mlir::Speculation::Speculatability", "getSpeculatability", (ins)> + ]; +} + +// Marks an Operation as always speculatable. +def AlwaysSpeculatable : TraitList<[ + ConditionallySpeculatable, AlwaysSpeculatableImplTrait]>; + +// Marks an Operation as speculatable only if all the operations in all attached +// regions are also speculatable. +def RecursivelySpeculatable : TraitList<[ + ConditionallySpeculatable, RecursivelySpeculatableImplTrait]>; + +// Always speculatable operation that does not touch memory. These operations +// are always legal to hoist or sink. +def Pure : TraitList<[AlwaysSpeculatable, NoMemoryEffect]>; + #endif // MLIR_INTERFACES_SIDEEFFECTS -- Gitee From 5ff6c31b6a7a7c9221eaa39a7d19443484ef48f5 Mon Sep 17 00:00:00 2001 From: zhaoxuhui Date: Tue, 21 Nov 2023 09:17:29 +0800 Subject: [PATCH 2/2] [BSC] Adapt to TableGen --- mlir/include/mlir/IR/DialectBase.td | 3 + mlir/include/mlir/IR/ExtensibleDialect.h | 33 + mlir/include/mlir/IR/OpBase.td | 4 + mlir/include/mlir/IR/OpDefinition.h | 126 ++ mlir/include/mlir/IR/Operation.h | 64 +- mlir/include/mlir/IR/OperationSupport.h | 100 ++ mlir/include/mlir/IR/Properties.td | 156 +++ mlir/include/mlir/TableGen/Class.h | 56 +- mlir/include/mlir/TableGen/Dialect.h | 4 + mlir/include/mlir/TableGen/Operator.h | 35 + mlir/include/mlir/TableGen/Property.h | 119 ++ mlir/lib/IR/MLIRContext.cpp | 91 ++ mlir/lib/IR/Operation.cpp | 85 +- mlir/lib/TableGen/CMakeLists.txt | 1 + mlir/lib/TableGen/Class.cpp | 25 + mlir/lib/TableGen/Dialect.cpp | 4 + mlir/lib/TableGen/Operator.cpp | 12 + mlir/lib/TableGen/Property.cpp | 71 + .../tools/mlir-tblgen/AttrOrTypeFormatGen.cpp | 92 +- mlir/tools/mlir-tblgen/FormatGen.cpp | 80 +- mlir/tools/mlir-tblgen/FormatGen.h | 63 +- mlir/tools/mlir-tblgen/OpClass.cpp | 5 + mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp | 1194 ++++++++++++++--- mlir/tools/mlir-tblgen/OpFormatGen.cpp | 293 ++-- 24 files changed, 2352 insertions(+), 364 deletions(-) create mode 100644 mlir/include/mlir/IR/Properties.td create mode 100644 mlir/include/mlir/TableGen/Property.h create mode 100644 mlir/lib/TableGen/Property.cpp diff --git a/mlir/include/mlir/IR/DialectBase.td b/mlir/include/mlir/IR/DialectBase.td index df0a164d0f5d..b207c03b1a55 100644 --- a/mlir/include/mlir/IR/DialectBase.td +++ b/mlir/include/mlir/IR/DialectBase.td @@ -101,6 +101,9 @@ class Dialect { // If this dialect can be extended at runtime with new operations or types. bit isExtensible = 0; + + // Whether inherent Attributes defined in ODS will be stored as Properties. + bit usePropertiesForAttributes = 1; } #endif // DIALECTBASE_TD diff --git a/mlir/include/mlir/IR/ExtensibleDialect.h b/mlir/include/mlir/IR/ExtensibleDialect.h index 84dc56749e77..f8f897814759 100644 --- a/mlir/include/mlir/IR/ExtensibleDialect.h +++ b/mlir/include/mlir/IR/ExtensibleDialect.h @@ -412,6 +412,39 @@ public: populateDefaultAttrsFn = std::move(populateDefaultAttrs); } + /// Implementation for properties (unsupported right now here). + Optional getInherentAttr(Operation *op, + StringRef name) final { + llvm::report_fatal_error("Unsupported getInherentAttr on Dynamic dialects"); + } + void setInherentAttr(Operation *op, StringAttr name, Attribute value) final { + llvm::report_fatal_error("Unsupported setInherentAttr on Dynamic dialects"); + } + void populateInherentAttrs(Operation *op, NamedAttrList &attrs) final {} + LogicalResult + verifyInherentAttrs(OperationName opName, NamedAttrList &attributes, + function_ref getDiag) final { + return success(); + } + int getOpPropertyByteSize() final { return 0; } + void initProperties(OperationName opName, OpaqueProperties storage, + OpaqueProperties init) final {} + void deleteProperties(OpaqueProperties prop) final {} + void populateDefaultProperties(OperationName opName, + OpaqueProperties properties) final {} + + LogicalResult + setPropertiesFromAttr(OperationName opName, OpaqueProperties properties, + Attribute attr, + function_ref getDiag) final { + getDiag() << "extensible Dialects don't support properties"; + return failure(); + } + Attribute getPropertiesAsAttr(Operation *op) final { return {}; } + void copyProperties(OpaqueProperties lhs, OpaqueProperties rhs) final {} + bool compareProperties(OpaqueProperties, OpaqueProperties) final { return false; } + llvm::hash_code hashProperties(OpaqueProperties prop) final { return {}; } + private: DynamicOpDefinition( StringRef name, ExtensibleDialect *dialect, diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td index 6eaa52effb08..8c7b8b397915 100644 --- a/mlir/include/mlir/IR/OpBase.td +++ b/mlir/include/mlir/IR/OpBase.td @@ -2256,6 +2256,10 @@ class Op props = []> { // Whether this op has a folder. bit hasFolder = 0; + // Whether to let ops implement their custom `readProperties` and + // `writeProperties` methods to emit bytecode. + bit useCustomPropertiesEncoding = 0; + // Op traits. // Note: The list of traits will be uniqued by ODS. list traits = props; diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h index 1e8f93a89573..2d0984f86a03 100644 --- a/mlir/include/mlir/IR/OpDefinition.h +++ b/mlir/include/mlir/IR/OpDefinition.h @@ -70,6 +70,21 @@ void ensureRegionTerminator( } // namespace impl +/// Structure used by default as a "marker" when no "Properties" are set on an +/// Operation. +struct EmptyProperties {}; + +/// Traits to detect whether an Operation defined a `Properties` type, otherwise +/// it'll default to `EmptyProperties`. +template +struct PropertiesSelector { + using type = EmptyProperties; +}; +template +struct PropertiesSelector> { + using type = typename Op::Properties; +}; + /// This is the concrete base class that holds the operation pointer and has /// non-generic methods that only depend on State (to avoid having them /// instantiated on template types that don't affect them. @@ -204,6 +219,13 @@ protected: /// in generic form. static void print(Operation *op, OpAsmPrinter &p, StringRef defaultDialect); + /// Parse properties as a Attribute. + static ParseResult genericParseProperties(OpAsmParser &parser, + Attribute &result); + + /// Print the properties as a Attribute. + static void genericPrintProperties(OpAsmPrinter &p, Attribute properties); + /// Print an operation name, eliding the dialect prefix if necessary. static void printOpName(Operation *op, OpAsmPrinter &p, StringRef defaultDialect); @@ -212,6 +234,14 @@ protected: /// so we can cast it away here. explicit OpState(Operation *state) : state(state) {} + /// For all op which don't have properties, we keep a single instance of + /// `EmptyProperties` to be used where a reference to a properties is needed: + /// this allow to bind a pointer to the reference without triggering UB. + static EmptyProperties &getEmptyProperties() { + static EmptyProperties emptyProperties; + return emptyProperties; + } + private: Operation *state; @@ -1697,6 +1727,35 @@ public: info->attachInterface(); } + /// Convert the provided attribute to a property and assigned it to the + /// provided properties. This default implementation forwards to a free + /// function `setPropertiesFromAttribute` that can be looked up with ADL in + /// the namespace where the properties are defined. It can also be overridden + /// in the derived ConcreteOp. + template + static LogicalResult + setPropertiesFromAttr(PropertiesTy &prop, Attribute attr, + function_ref getDiag) { + return setPropertiesFromAttribute(prop, attr, getDiag); + } + /// Convert the provided properties to an attribute. This default + /// implementation forwards to a free function `getPropertiesAsAttribute` that + /// can be looked up with ADL in the namespace where the properties are + /// defined. It can also be overridden in the derived ConcreteOp. + template + static Attribute getPropertiesAsAttr(MLIRContext *ctx, + const PropertiesTy &prop) { + return getPropertiesAsAttribute(ctx, prop); + } + /// Hash the provided properties. This default implementation forwards to a + /// free function `computeHash` that can be looked up with ADL in the + /// namespace where the properties are defined. It can also be overridden in + /// the derived ConcreteOp. + template + static llvm::hash_code computePropertiesHash(const PropertiesTy &prop) { + return computeHash(prop); + } + private: /// Trait to check if T provides a 'fold' method for a single result op. template @@ -1719,10 +1778,34 @@ private: template using detect_has_print = llvm::is_detected; + /// Trait to check if printProperties(OpAsmPrinter, T) exist + template + using has_print_properties = decltype(printProperties( + std::declval(), std::declval())); + template + using detect_has_print_properties = + llvm::is_detected; + + /// Trait to check if parseProperties(OpAsmParser, T) exist + template + using has_parse_properties = decltype(parseProperties( + std::declval(), std::declval())); + template + using detect_has_parse_properties = + llvm::is_detected; + /// Trait to check if T provides a 'ConcreteEntity' type alias. template using has_concrete_entity_t = typename T::ConcreteEntity; +public: + /// Returns true if this operation defines a `Properties` inner type. + static constexpr bool hasProperties() { + return !std::is_same_v< + typename ConcreteType::template InferredProperties, + EmptyProperties>; + } + /// A struct-wrapped type alias to T::ConcreteEntity if provided and to /// ConcreteType otherwise. This is akin to std::conditional but doesn't fail /// on the missing typedef. Useful for checking if the interface is targeting @@ -1879,6 +1962,49 @@ private: static OperationName::PopulateDefaultAttrsFn getPopulateDefaultAttrsFn() { return ConcreteType::populateDefaultAttrs; } + +public: + template + using InferredProperties = typename PropertiesSelector::type; + template + InferredProperties &getProperties() { + if constexpr (!hasProperties()) + return getEmptyProperties(); + return *getOperation() + ->getPropertiesStorage() + .template as *>(); + } + + /// This hook populates any unset default attrs when mapped to properties. + template + static void populateDefaultProperties(OperationName opName, + InferredProperties &properties) {} + + /// Print the operation properties. Unless overridden, this method will try to + /// dispatch to a `printProperties` free-function if it exists, and otherwise + /// by converting the properties to an Attribute. + template + static void printProperties(MLIRContext *ctx, OpAsmPrinter &p, + const T &properties) { + if constexpr (detect_has_print_properties::value) + return printProperties(p, properties); + genericPrintProperties(p, + ConcreteType::getPropertiesAsAttr(ctx, properties)); + } + + /// Parser the properties. Unless overridden, this method will print by + /// converting the properties to an Attribute. + template + static ParseResult parseProperties(OpAsmParser &parser, + OperationState &result) { + if constexpr (detect_has_parse_properties>::value) { + return parseProperties( + parser, result.getOrAddProperties>()); + } + return genericParseProperties(parser, result.propertiesAttr); + } + +private: /// Implementation of `VerifyInvariantsFn` OperationName hook. static LogicalResult verifyInvariants(Operation *op) { static_assert(hasNoDataMembers(), diff --git a/mlir/include/mlir/IR/Operation.h b/mlir/include/mlir/IR/Operation.h index f785214cd8cb..23982a27d88a 100644 --- a/mlir/include/mlir/IR/Operation.h +++ b/mlir/include/mlir/IR/Operation.h @@ -21,6 +21,12 @@ #include "llvm/ADT/Twine.h" namespace mlir { +namespace detail { +/// This is a "tag" used for mapping the properties storage in +/// llvm::TrailingObjects. +enum class OpProperties : char {}; +} // namespace detail + /// Operation is a basic unit of execution within MLIR. Operations can /// be nested within `Region`s held by other operations effectively forming a /// tree. Child operations are organized into operation blocks represented by a @@ -28,7 +34,8 @@ namespace mlir { class alignas(8) Operation final : public llvm::ilist_node_with_parent, private llvm::TrailingObjects { + detail::OpProperties, BlockOperand, Region, + OpOperand> { public: /// Create a new Operation with the specific fields. static Operation *create(Location location, OperationName name, @@ -669,6 +676,45 @@ public: /// handlers that may be listening. InFlightDiagnostic emitRemark(const Twine &message = {}); + /// Returns the properties storage size. + int getPropertiesStorageSize() const { + return ((int)propertiesStorageSize) * 8; + } + /// Returns the properties storage. + OpaqueProperties getPropertiesStorage() { + if (propertiesStorageSize) + return { + reinterpret_cast(getTrailingObjects())}; + return {nullptr}; + } + OpaqueProperties getPropertiesStorage() const { + if (propertiesStorageSize) + return {reinterpret_cast(const_cast( + getTrailingObjects()))}; + return {nullptr}; + } + + /// Return the properties converted to an attribute. + /// This is expensive, and mostly useful when dealing with unregistered + /// operation. Returns an empty attribute if no properties are present. + Attribute getPropertiesAsAttribute(); + + /// Set the properties from the provided attribute. + /// This is an expensive operation that can fail if the attribute is not + /// matching the expectations of the properties for this operation. This is + /// mostly useful for unregistered operations or used when parsing the + /// generic format. An optional diagnostic can be passed in for richer errors. + LogicalResult + setPropertiesFromAttribute(Attribute attr, + function_ref getDiag); + + /// Copy properties from an existing other properties object. The two objects + /// must be the same type. + void copyProperties(OpaqueProperties rhs); + + /// Compute a hash for the op properties (if any). + llvm::hash_code hashProperties(); + private: //===--------------------------------------------------------------------===// // Ordering @@ -771,6 +817,14 @@ private: /// operands. bool hasOperandStorage : 1; + /// The size of the storage for properties (if any), divided by 8: since the + /// Properties storage will always be rounded up to the next multiple of 8 we + /// save some bits here. + unsigned char propertiesStorageSize : 8; + /// This is the maximum size we support to allocate properties inline with an + /// operation: this must match the bitwidth above. + static constexpr int64_t propertiesCapacity = 8 * 256; + /// This holds the name of the operation. OperationName name; @@ -790,8 +844,9 @@ private: friend class llvm::ilist_node_with_parent; // This stuff is used by the TrailingObjects template. - friend llvm::TrailingObjects; + friend llvm::TrailingObjects; size_t numTrailingObjects(OverloadToken) const { return hasOperandStorage ? 1 : 0; } @@ -799,6 +854,9 @@ private: return numSuccs; } size_t numTrailingObjects(OverloadToken) const { return numRegions; } + size_t numTrailingObjects(OverloadToken) const { + return getPropertiesStorageSize(); + } }; inline raw_ostream &operator<<(raw_ostream &os, const Operation &op) { diff --git a/mlir/include/mlir/IR/OperationSupport.h b/mlir/include/mlir/IR/OperationSupport.h index b42ef52cd314..f34031cae0d7 100644 --- a/mlir/include/mlir/IR/OperationSupport.h +++ b/mlir/include/mlir/IR/OperationSupport.h @@ -57,6 +57,25 @@ class ValueRange; template class ValueTypeRange; +//===----------------------------------------------------------------------===// +// OpaqueProperties +//===----------------------------------------------------------------------===// + +/// Simple wrapper around a void* in order to express generically how to pass +/// in op properties through APIs. +class OpaqueProperties { +public: + OpaqueProperties(void *prop) : properties(prop) {} + operator bool() const { return properties != nullptr; } + template + Dest as() const { + return static_cast(const_cast(properties)); + } + +private: + void *properties; +}; + //===----------------------------------------------------------------------===// // OperationName //===----------------------------------------------------------------------===// @@ -81,6 +100,47 @@ public: using VerifyRegionInvariantsFn = llvm::unique_function; + /// This class represents a type erased version of an operation. It contains + /// all of the components necessary for opaquely interacting with an + /// operation. If the operation is not registered, some of these components + /// may not be populated. + struct InterfaceConcept { + virtual ~InterfaceConcept() = default; + virtual LogicalResult foldHook(Operation *, ArrayRef, + SmallVectorImpl &) = 0; + virtual void getCanonicalizationPatterns(RewritePatternSet &, + MLIRContext *) = 0; + virtual bool hasTrait(TypeID) = 0; + virtual OperationName::ParseAssemblyFn getParseAssemblyFn() = 0; + virtual void populateDefaultAttrs(const OperationName &, + NamedAttrList &) = 0; + virtual void printAssembly(Operation *, OpAsmPrinter &, StringRef) = 0; + virtual LogicalResult verifyInvariants(Operation *) = 0; + virtual LogicalResult verifyRegionInvariants(Operation *) = 0; + /// Implementation for properties + virtual Optional getInherentAttr(Operation *, + StringRef name) = 0; + virtual void setInherentAttr(Operation *op, StringAttr name, + Attribute value) = 0; + virtual void populateInherentAttrs(Operation *op, NamedAttrList &attrs) = 0; + virtual LogicalResult + verifyInherentAttrs(OperationName opName, NamedAttrList &attributes, + function_ref getDiag) = 0; + virtual int getOpPropertyByteSize() = 0; + virtual void initProperties(OperationName opName, OpaqueProperties storage, + OpaqueProperties init) = 0; + virtual void deleteProperties(OpaqueProperties) = 0; + virtual void populateDefaultProperties(OperationName opName, + OpaqueProperties properties) = 0; + virtual LogicalResult + setPropertiesFromAttr(OperationName, OpaqueProperties, Attribute, + function_ref getDiag) = 0; + virtual Attribute getPropertiesAsAttr(Operation *) = 0; + virtual void copyProperties(OpaqueProperties, OpaqueProperties) = 0; + virtual bool compareProperties(OpaqueProperties, OpaqueProperties) = 0; + virtual llvm::hash_code hashProperties(OpaqueProperties) = 0; + }; + protected: /// This class represents a type erased version of an operation. It contains /// all of the components necessary for opaquely interacting with an @@ -637,6 +697,19 @@ struct OperationState { /// Regions that the op will hold. SmallVector, 1> regions; + // If we're creating an unregistered operation, this Attribute is used to + // build the properties. Otherwise it is ignored. For registered operations + // see the `getOrAddProperties` method. + Attribute propertiesAttr; + +private: + OpaqueProperties properties = nullptr; + TypeID propertiesId; + llvm::function_ref propertiesDeleter; + llvm::function_ref + propertiesSetter; + friend class Operation; + public: OperationState(Location location, StringRef name); OperationState(Location location, OperationName name); @@ -650,6 +723,33 @@ public: BlockRange successors = {}, MutableArrayRef> regions = {}); + /// Get (or create) a properties of the provided type to be set on the + /// operation on creation. + template + T &getOrAddProperties() { + if (!properties) { + T *p = new T{}; + properties = p; + propertiesDeleter = [](OpaqueProperties prop) { + delete prop.as(); + }; + propertiesSetter = [](OpaqueProperties new_prop, + const OpaqueProperties prop) { + *new_prop.as() = *prop.as(); + }; + propertiesId = TypeID::get(); + } + assert(propertiesId == TypeID::get() && "Inconsistent properties"); + return *properties.as(); + } + OpaqueProperties getRawProperties() { return properties; } + + // Set the properties defined on this OpState on the given operation, + // optionally emit diagnostics on error through the provided diagnostic. + LogicalResult + setProperties(Operation *op, + function_ref getDiag) const; + void addOperands(ValueRange newOperands); void addTypes(ArrayRef newTypes) { diff --git a/mlir/include/mlir/IR/Properties.td b/mlir/include/mlir/IR/Properties.td new file mode 100644 index 000000000000..99da1763524f --- /dev/null +++ b/mlir/include/mlir/IR/Properties.td @@ -0,0 +1,156 @@ +//===-- Properties.td - Properties definition file ----------------*- tablegen -*-===// +// +// 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 is the base properties defination file. +// +//===----------------------------------------------------------------------===// + +#ifndef PROPERTIES +#define PROPERTIES + +// Base class for defining properties. +class Property { + // User-readable one line summary used in error reporting messages. If empty, + // a generic message will be used. + string summary = desc; + // The full description of this property. + string description = ""; + code storageType = storageTypeParam; + code interfaceType = storageTypeParam; + + // The expression to convert from the storage type to the Interface + // type. For example, an enum can be stored as an int but returned as an + // enum class. + // + // Format: + // - `$_storage` will contain the property in the storage type. + // - `$_ctxt` will contain an `MLIRContext *`. + code convertFromStorage = "$_storage"; + + // The call expression to build a property storage from the interface type. + // + // Format: + // - `$_storage` will contain the property in the storage type. + // - `$_value` will contain the property in the user interface type. + code assignToStorage = "$_storage = $_value"; + + // The call expression to convert from the storage type to an attribute. + // + // Format: + // - `$_storage` is the storage type value. + // - `$_ctxt` is a `MLIRContext *`. + // + // The expression must result in an Attribute. + code convertToAttribute = [{ + convertToAttribute($_ctxt, $_storage) + }]; + + // The call expression to convert from an Attribute to the storage type. + // + // Format: + // - `$_storage` is the storage type value. + // - `$_attr` is the attribute. + // - `$_diag` is a callback to get a Diagnostic to emit error. + // + // The expression must return a LogicalResult + code convertFromAttribute = [{ + return convertFromAttribute($_storage, $_attr, $_diag); + }]; + + // The call expression to hash the property. + // + // Format: + // - `$_storage` is the variable to hash. + // + // The expression should define a llvm::hash_code. + code hashProperty = [{ + llvm::hash_value($_storage); + }]; + + // The call expression to emit the storage type to bytecode. + // + // Format: + // - `$_storage` is the storage type value. + // - `$_writer` is a `DialectBytecodeWriter`. + // - `$_ctxt` is a `MLIRContext *`. + code writeToMlirBytecode = [{ + writeToMlirBytecode($_writer, $_storage) + }]; + + // The call expression to read the storage type from bytecode. + // + // Format: + // - `$_storage` is the storage type value. + // - `$_reader` is a `DialectBytecodeReader`. + // - `$_ctxt` is a `MLIRContext *`. + code readFromMlirBytecode = [{ + if (::mlir::failed(readFromMlirBytecode($_reader, $_storage))) + return ::mlir::failure(); + }]; + + // Default value for the property. + string defaultValue = ?; +} + +/// Implementation of the Property class's `readFromMlirBytecode` field using +/// the default `convertFromAttribute` implementation. +/// Users not wanting to implement their own `readFromMlirBytecode` and +/// `writeToMlirBytecode` implementations can opt into using this implementation +/// by writing: +/// +/// let writeToMlirBytecode = writeMlirBytecodeWithConvertToAttribute; +/// let readFromMlirBytecode = readMlirBytecodeUsingConvertFromAttribute; +/// +/// in their property definition. +/// Serialization and deserialization is performed using the attributes +/// returned by `convertFromAttribute` and `convertToAttribute`. +/// +/// WARNING: This implementation creates a less than optimal encoding. +/// Users caring about optimal encoding should not use this implementation and +/// implement `readFromMlirBytecode` and `writeToMlirBytecode` themselves. +defvar readMlirBytecodeUsingConvertFromAttribute = [{ + ::mlir::Attribute attr; + if (::mlir::failed($_reader.readAttribute(attr))) + return ::mlir::failure(); + if (::mlir::failed(convertFromAttribute($_storage, attr, nullptr))) + return ::mlir::failure(); +}]; + +/// Implementation of the Property class's `writeToMlirBytecode` field using +/// the default `convertToAttribute` implementation. +/// See description of `readMlirBytecodeUsingConvertFromAttribute` above for +/// details. +defvar writeMlirBytecodeWithConvertToAttribute = [{ + $_writer.writeAttribute(convertToAttribute($_ctxt, $_storage)) +}]; + +//===----------------------------------------------------------------------===// +// Primitive property kinds + +// Any kind of integer stored as properties. +class IntProperty : + Property { + code writeToMlirBytecode = [{ + $_writer.writeVarInt($_storage); + }]; + code readFromMlirBytecode = [{ + uint64_t val; + if (failed($_reader.readVarInt(val))) + return ::mlir::failure(); + $_storage = val; + }]; +} + +class ArrayProperty : + Property { + let interfaceType = "::llvm::ArrayRef<" # storageTypeParam # ">"; + let convertFromStorage = "$_storage"; + let assignToStorage = "::llvm::copy($_value, $_storage)"; +} + +#endif // PROPERTIES diff --git a/mlir/include/mlir/TableGen/Class.h b/mlir/include/mlir/TableGen/Class.h index efcd73ae61de..3f2ca742c759 100644 --- a/mlir/include/mlir/TableGen/Class.h +++ b/mlir/include/mlir/TableGen/Class.h @@ -152,6 +152,9 @@ public: /// Get the name of the method. StringRef getName() const { return methodName; } + /// Get the return type of the method + StringRef getReturnType() const { return returnType; } + /// Get the number of parameters. unsigned getNumParameters() const { return parameters.getNumParameters(); } @@ -163,6 +166,15 @@ public: /// method definition). void writeDefTo(raw_indented_ostream &os, StringRef namePrefix) const; + /// Write the template parameters of the signature. + void writeTemplateParamsTo(raw_indented_ostream &os) const; + + /// Add a template parameter. + template + void addTemplateParam(ParamT param) { + templateParams.push_back(stringify(param)); + } + private: /// The method's C++ return type. std::string returnType; @@ -170,6 +182,8 @@ private: std::string methodName; /// The method's parameter list. MethodParameters parameters; + /// An optional list of template parameters. + SmallVector templateParams; }; /// This class contains the body of a C++ method. @@ -344,6 +358,9 @@ public: /// Returns the name of this method. StringRef getName() const { return methodSignature.getName(); } + /// Returns the return type of this method + StringRef getReturnType() const { return methodSignature.getReturnType(); } + /// Returns if this method makes the `other` method redundant. bool makesRedundant(const Method &other) const { return methodSignature.makesRedundant(other.methodSignature); @@ -356,6 +373,10 @@ public: void writeDefTo(raw_indented_ostream &os, StringRef namePrefix) const override; + /// Add a template parameter. + template + void addTemplateParam(ParamT param); + protected: /// A collection of method properties. Properties properties; @@ -363,6 +384,8 @@ protected: MethodSignature methodSignature; /// The body of the method, if it has one. MethodBody methodBody; + /// Deprecation message if the method is deprecated. + Optional deprecationMessage; }; /// This enum describes C++ inheritance visibility. @@ -439,6 +462,13 @@ operator|(mlir::tblgen::Method::Properties lhs, namespace mlir { namespace tblgen { +template +void Method::addTemplateParam(ParamT param) { + // Templates imply inline. + properties = properties | Method::Inline; + methodSignature.addTemplateParam(param); +} + /// This class describes a C++ parent class declaration. class ParentClass { public: @@ -488,11 +518,21 @@ public: /// Write the using declaration. void writeDeclTo(raw_indented_ostream &os) const override; + /// Add a template parameter. + template + void addTemplateParam(ParamT param) { + templateParams.insert(stringify(param)); + } + private: /// The name of the declaration, or a resolved name to an inherited function. std::string name; /// The type that is being aliased. Leave empty for inheriting functions. std::string value; + /// An optional list of class template parameters. + /// This is simply a ordered list of parameter names that are then added as + /// template type parameters when the using declaration is emitted. + SetVector, StringSet<>> templateParams; }; /// This class describes a class field. @@ -583,8 +623,13 @@ public: /// returns a pointer to the new constructor. template Constructor *addConstructor(Args &&...args) { + Method::Properties defaultProperties = Method::Constructor; + // If the class has template parameters, the constructor has to be defined + // inline. + if (!templateParams.empty()) + defaultProperties = defaultProperties | Method::Inline; return addConstructorAndPrune(Constructor(getClassName(), - Properties | Method::Constructor, + Properties | defaultProperties, std::forward(args)...)); } @@ -674,6 +719,12 @@ public: /// Add a parent class. ParentClass &addParent(ParentClass parent); + /// Add a template parameter. + template + void addTemplateParam(ParamT param) { + templateParams.insert(stringify(param)); + } + /// Return the C++ name of the class. StringRef getClassName() const { return className; } @@ -751,6 +802,9 @@ protected: /// A list of declarations in the class, emitted in order. std::vector> declarations; + + /// An optional list of class template parameters. + SetVector, StringSet<>> templateParams; }; } // namespace tblgen diff --git a/mlir/include/mlir/TableGen/Dialect.h b/mlir/include/mlir/TableGen/Dialect.h index 297d2df98833..9358cf9b98a5 100644 --- a/mlir/include/mlir/TableGen/Dialect.h +++ b/mlir/include/mlir/TableGen/Dialect.h @@ -86,6 +86,10 @@ public: /// operations or types. bool isExtensible() const; + /// Default to use properties for storing Attributes for operations in this + /// dialect. + bool usePropertiesForAttributes() const; + // Returns whether two dialects are equal by checking the equality of the // underlying record. bool operator==(const Dialect &other) const; diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h index ab03a1af8a24..a3a614601b80 100644 --- a/mlir/include/mlir/TableGen/Operator.h +++ b/mlir/include/mlir/TableGen/Operator.h @@ -18,6 +18,7 @@ #include "mlir/TableGen/Attribute.h" #include "mlir/TableGen/Builder.h" #include "mlir/TableGen/Dialect.h" +#include "mlir/TableGen/Property.h" #include "mlir/TableGen/Region.h" #include "mlir/TableGen/Successor.h" #include "mlir/TableGen/Trait.h" @@ -64,6 +65,9 @@ public: // Returns the name of op's adaptor C++ class. std::string getAdaptorName() const; + /// Returns the name of op's generic adaptor C++ class. + std::string getGenericAdaptorName() const; + // Check invariants (like no duplicated or conflicted names) and abort the // process if any invariant is broken. void assertInvariants() const; @@ -149,6 +153,27 @@ public: const_value_iterator operand_end() const; const_value_range getOperands() const; + // Op properties iterators. + using const_property_iterator = const NamedProperty *; + const_property_iterator properties_begin() const { + return properties.begin(); + } + const_property_iterator properties_end() const { return properties.end(); } + llvm::iterator_range getProperties() const { + return properties; + } + using property_iterator = NamedProperty *; + property_iterator properties_begin() { return properties.begin(); } + property_iterator properties_end() { return properties.end(); } + llvm::iterator_range getProperties() { return properties; } + int getNumCoreAttributes() const { return properties.size(); } + + // Op properties accessors. + NamedProperty &getProperty(int index) { return properties[index]; } + const NamedProperty &getProperty(int index) const { + return properties[index]; + } + int getNumOperands() const { return operands.size(); } NamedTypeConstraint &getOperand(int index) { return operands[index]; } const NamedTypeConstraint &getOperand(int index) const { @@ -313,6 +338,13 @@ public: // Returns the setter names for the accessor. SmallVector getSetterNames(StringRef name) const; + /// Returns the remove name for the accessor of `name`. + std::string getRemoverName(StringRef name) const; + + /// Whether to generate the `readProperty`/`writeProperty` methods for + /// bytecode emission. + bool useCustomPropertiesEncoding() const; + private: // Populates the vectors containing operands, attributes, results and traits. void populateOpStructure(); @@ -340,6 +372,9 @@ private: // upon request). SmallVector attributes; + /// The properties of the op. + SmallVector properties; + // The arguments of the op (operands and native attributes). SmallVector arguments; diff --git a/mlir/include/mlir/TableGen/Property.h b/mlir/include/mlir/TableGen/Property.h new file mode 100644 index 000000000000..d0d6f4940c7c --- /dev/null +++ b/mlir/include/mlir/TableGen/Property.h @@ -0,0 +1,119 @@ +//===- Property.h - Property wrapper class --------------------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Property wrapper to simplify using TableGen Record defining a MLIR +// Property. +// +//===----------------------------------------------------------------------===// + +#ifndef MLIR_TABLEGEN_PROPERTY_H_ +#define MLIR_TABLEGEN_PROPERTY_H_ + +#include "mlir/Support/LLVM.h" +#include "mlir/TableGen/Constraint.h" +#include "llvm/ADT/StringRef.h" + +namespace llvm { +class DefInit; +class Record; +} // namespace llvm + +namespace mlir { +namespace tblgen { +class Dialect; +class Type; + +// Wrapper class providing helper methods for accessing MLIR Property defined +// in TableGen. This class should closely reflect what is defined as class +// `Property` in TableGen. +class Property { +public: + explicit Property(const llvm::Record *record); + explicit Property(const llvm::DefInit *init); + Property(StringRef storageType, StringRef interfaceType, + StringRef convertFromStorageCall, StringRef assignToStorageCall, + StringRef convertToAttributeCall, StringRef convertFromAttributeCall, + StringRef readFromMlirBytecodeCall, + StringRef writeToMlirBytecodeCall, StringRef hashPropertyCall, + StringRef defaultValue); + + // Returns the storage type. + StringRef getStorageType() const { return storageType; } + + // Returns the interface type for this property. + StringRef getInterfaceType() const { return interfaceType; } + + // Returns the template getter method call which reads this property's + // storage and returns the value as of the desired return type. + StringRef getConvertFromStorageCall() const { return convertFromStorageCall; } + + // Returns the template setter method call which reads this property's + // in the provided interface type and assign it to the storage. + StringRef getAssignToStorageCall() const { return assignToStorageCall; } + + // Returns the conversion method call which reads this property's + // in the storage type and builds an attribute. + StringRef getConvertToAttributeCall() const { return convertToAttributeCall; } + + // Returns the setter method call which reads this property's + // in the provided interface type and assign it to the storage. + StringRef getConvertFromAttributeCall() const { + return convertFromAttributeCall; + } + + // Returns the method call which reads this property from + // bytecode and assign it to the storage. + StringRef getReadFromMlirBytecodeCall() const { + return readFromMlirBytecodeCall; + } + + // Returns the method call which write this property's + // to the the bytecode. + StringRef getWriteToMlirBytecodeCall() const { + return writeToMlirBytecodeCall; + } + + // Returns the code to compute the hash for this property. + StringRef getHashPropertyCall() const { return hashPropertyCall; } + + // Returns whether this Property has a default value. + bool hasDefaultValue() const { return !defaultValue.empty(); } + + // Returns the default value for this Property. + StringRef getDefaultValue() const { return defaultValue; } + + // Returns the TableGen definition this Property was constructed from. + const llvm::Record &getDef() const { return *def; } + +private: + // The TableGen definition of this constraint. + const llvm::Record *def; + + // Elements describing a Property, in general fetched from the record. + StringRef storageType; + StringRef interfaceType; + StringRef convertFromStorageCall; + StringRef assignToStorageCall; + StringRef convertToAttributeCall; + StringRef convertFromAttributeCall; + StringRef readFromMlirBytecodeCall; + StringRef writeToMlirBytecodeCall; + StringRef hashPropertyCall; + StringRef defaultValue; +}; + +// A struct wrapping an op property and its name together +struct NamedProperty { + llvm::StringRef name; + Property prop; +}; + +} // namespace tblgen +} // namespace mlir + +#endif // MLIR_TABLEGEN_PROPERTY_H_ diff --git a/mlir/lib/IR/MLIRContext.cpp b/mlir/lib/IR/MLIRContext.cpp index 273faa89b826..d4c75238f740 100644 --- a/mlir/lib/IR/MLIRContext.cpp +++ b/mlir/lib/IR/MLIRContext.cpp @@ -688,6 +688,97 @@ StringRef OperationName::getDialectNamespace() const { return getStringRef().split('.').first; } +LogicalResult +OperationName::UnregisteredOpModel::foldHook(Operation *, ArrayRef, + SmallVectorImpl &) { + return failure(); +} +void OperationName::UnregisteredOpModel::getCanonicalizationPatterns( + RewritePatternSet &, MLIRContext *) {} +bool OperationName::UnregisteredOpModel::hasTrait(TypeID) { return false; } + +OperationName::ParseAssemblyFn +OperationName::UnregisteredOpModel::getParseAssemblyFn() { + llvm::report_fatal_error("getParseAssemblyFn hook called on unregistered op"); +} +void OperationName::UnregisteredOpModel::populateDefaultAttrs( + const OperationName &, NamedAttrList &) {} +void OperationName::UnregisteredOpModel::printAssembly( + Operation *op, OpAsmPrinter &p, StringRef defaultDialect) { + p.printGenericOp(op); +} +LogicalResult +OperationName::UnregisteredOpModel::verifyInvariants(Operation *) { + return success(); +} +LogicalResult +OperationName::UnregisteredOpModel::verifyRegionInvariants(Operation *) { + return success(); +} + +Optional +OperationName::UnregisteredOpModel::getInherentAttr(Operation *op, + StringRef name) { + auto dict = dyn_cast_or_null(getPropertiesAsAttr(op)); + if (!dict) + return llvm::None; + if (Attribute attr = dict.get(name)) + return attr; + return llvm::None; +} +void OperationName::UnregisteredOpModel::setInherentAttr(Operation *op, + StringAttr name, + Attribute value) { + auto dict = dyn_cast_or_null(getPropertiesAsAttr(op)); + assert(dict); + NamedAttrList attrs(dict); + attrs.set(name, value); + *op->getPropertiesStorage().as() = + attrs.getDictionary(op->getContext()); +} +void OperationName::UnregisteredOpModel::populateInherentAttrs( + Operation *op, NamedAttrList &attrs) {} +LogicalResult OperationName::UnregisteredOpModel::verifyInherentAttrs( + OperationName opName, NamedAttrList &attributes, + function_ref getDiag) { + return success(); +} +int OperationName::UnregisteredOpModel::getOpPropertyByteSize() { + return sizeof(Attribute); +} +void OperationName::UnregisteredOpModel::initProperties( + OperationName opName, OpaqueProperties storage, OpaqueProperties init) { + new (storage.as()) Attribute(); +} +void OperationName::UnregisteredOpModel::deleteProperties( + OpaqueProperties prop) { + prop.as()->~Attribute(); +} +void OperationName::UnregisteredOpModel::populateDefaultProperties( + OperationName opName, OpaqueProperties properties) {} +LogicalResult OperationName::UnregisteredOpModel::setPropertiesFromAttr( + OperationName opName, OpaqueProperties properties, Attribute attr, + function_ref getDiag) { + *properties.as() = attr; + return success(); +} +Attribute +OperationName::UnregisteredOpModel::getPropertiesAsAttr(Operation *op) { + return *op->getPropertiesStorage().as(); +} +void OperationName::UnregisteredOpModel::copyProperties(OpaqueProperties lhs, + OpaqueProperties rhs) { + *lhs.as() = *rhs.as(); +} +bool OperationName::UnregisteredOpModel::compareProperties(OpaqueProperties lhs, + OpaqueProperties rhs) { + return *lhs.as() == *rhs.as(); +} +llvm::hash_code +OperationName::UnregisteredOpModel::hashProperties(OpaqueProperties prop) { + return llvm::hash_combine(*prop.as()); +} + //===----------------------------------------------------------------------===// // RegisteredOperationName //===----------------------------------------------------------------------===// diff --git a/mlir/lib/IR/Operation.cpp b/mlir/lib/IR/Operation.cpp index b255e80c0960..28a8b733e171 100644 --- a/mlir/lib/IR/Operation.cpp +++ b/mlir/lib/IR/Operation.cpp @@ -59,6 +59,7 @@ Operation *Operation::create(Location location, OperationName name, unsigned numSuccessors = successors.size(); unsigned numOperands = operands.size(); unsigned numResults = resultTypes.size(); + int opPropertiesAllocSize = llvm::alignTo<8>(name.getOpPropertyByteSize()); // If the operation is known to have no operands, don't allocate an operand // storage. @@ -69,8 +70,10 @@ Operation *Operation::create(Location location, OperationName name, // into account the size of the operation, its trailing objects, and its // prefixed objects. size_t byteSize = - totalSizeToAlloc( - needsOperandStorage ? 1 : 0, numSuccessors, numRegions, numOperands); + totalSizeToAlloc( + needsOperandStorage ? 1 : 0, opPropertiesAllocSize, numSuccessors, + numRegions, numOperands); size_t prefixByteSize = llvm::alignTo( Operation::prefixAllocSize(numTrailingResults, numInlineResults), alignof(Operation)); @@ -246,6 +249,84 @@ InFlightDiagnostic Operation::emitRemark(const Twine &message) { return diag; } +ictionaryAttr Operation::getAttrDictionary() { + if (getPropertiesStorageSize()) { + NamedAttrList attrsList = attrs; + getName().populateInherentAttrs(this, attrsList); + return attrsList.getDictionary(getContext()); + } + return attrs; +} + +void Operation::setAttrs(DictionaryAttr newAttrs) { + assert(newAttrs && "expected valid attribute dictionary"); + if (getPropertiesStorageSize()) { + // We're spliting the providing DictionaryAttr by removing the inherentAttr + // which will be stored in the properties. + SmallVector discardableAttrs; + discardableAttrs.reserve(newAttrs.size()); + for (NamedAttribute attr : newAttrs) { + if (getInherentAttr(attr.getName())) + setInherentAttr(attr.getName(), attr.getValue()); + else + discardableAttrs.push_back(attr); + } + if (discardableAttrs.size() != newAttrs.size()) + newAttrs = DictionaryAttr::get(getContext(), discardableAttrs); + } + attrs = newAttrs; +} +void Operation::setAttrs(ArrayRef newAttrs) { + if (getPropertiesStorageSize()) { + // We're spliting the providing array of attributes by removing the inherentAttr + // which will be stored in the properties. + SmallVector discardableAttrs; + discardableAttrs.reserve(newAttrs.size()); + for (NamedAttribute attr : newAttrs) { + if (getInherentAttr(attr.getName())) + setInherentAttr(attr.getName(), attr.getValue()); + else + discardableAttrs.push_back(attr); + } + attrs = DictionaryAttr::get(getContext(), discardableAttrs); + return; + } + attrs = DictionaryAttr::get(getContext(), newAttrs); +} + +Optional Operation::getInherentAttr(StringRef name) { + return getName().getInherentAttr(this, name); +} + +void Operation::setInherentAttr(StringAttr name, Attribute value) { + getName().setInherentAttr(this, name, value); +} + +Attribute Operation::getPropertiesAsAttribute() { + Optional info = getRegisteredInfo(); + if (LLVM_UNLIKELY(!info)) + return *getPropertiesStorage().as(); + return info->getOpPropertiesAsAttribute(this); +} +LogicalResult Operation::setPropertiesFromAttribute( + Attribute attr, function_ref getDiag) { + Optional info = getRegisteredInfo(); + if (LLVM_UNLIKELY(!info)) { + *getPropertiesStorage().as() = attr; + return success(); + } + return info->setOpPropertiesFromAttribute( + this->getName(), this->getPropertiesStorage(), attr, getDiag); +} + +void Operation::copyProperties(OpaqueProperties rhs) { + name.copyOpProperties(getPropertiesStorage(), rhs); +} + +llvm::hash_code Operation::hashProperties() { + return name.hashOpProperties(getPropertiesStorage()); +} + //===----------------------------------------------------------------------===// // Operation Ordering //===----------------------------------------------------------------------===// diff --git a/mlir/lib/TableGen/CMakeLists.txt b/mlir/lib/TableGen/CMakeLists.txt index bb522d7d03f4..e3529c7e5ea0 100644 --- a/mlir/lib/TableGen/CMakeLists.txt +++ b/mlir/lib/TableGen/CMakeLists.txt @@ -22,6 +22,7 @@ llvm_add_library(MLIRTableGen STATIC Pass.cpp Pattern.cpp Predicate.cpp + Property.cpp Region.cpp SideEffects.cpp Successor.cpp diff --git a/mlir/lib/TableGen/Class.cpp b/mlir/lib/TableGen/Class.cpp index a7c02d3ae543..effd89812379 100644 --- a/mlir/lib/TableGen/Class.cpp +++ b/mlir/lib/TableGen/Class.cpp @@ -93,6 +93,17 @@ void MethodSignature::writeDefTo(raw_indented_ostream &os, os << ")"; } +void MethodSignature::writeTemplateParamsTo( + mlir::raw_indented_ostream &os) const { + if (templateParams.empty()) + return; + + os << "template <"; + llvm::interleaveComma(templateParams, os, + [&](StringRef param) { os << "typename " << param; }); + os << ">\n"; +} + //===----------------------------------------------------------------------===// // MethodBody definitions //===----------------------------------------------------------------------===// @@ -114,6 +125,12 @@ void MethodBody::writeTo(raw_indented_ostream &os) const { //===----------------------------------------------------------------------===// void Method::writeDeclTo(raw_indented_ostream &os) const { + methodSignature.writeTemplateParamsTo(os); + if (deprecationMessage) { + os << "[[deprecated(\""; + os.write_escaped(*deprecationMessage); + os << "\")]]\n"; + } if (isStatic()) os << "static "; if (properties & ConstexprValue) @@ -148,6 +165,7 @@ void Method::writeDefTo(raw_indented_ostream &os, StringRef namePrefix) const { //===----------------------------------------------------------------------===// void Constructor::writeDeclTo(raw_indented_ostream &os) const { + methodSignature.writeTemplateParamsTo(os); if (properties & ConstexprValue) os << "constexpr "; methodSignature.writeDeclTo(os); @@ -275,6 +293,13 @@ ParentClass &Class::addParent(ParentClass parent) { } void Class::writeDeclTo(raw_indented_ostream &os) const { + if (!templateParams.empty()) { + os << "template <"; + llvm::interleaveComma(templateParams, os, + [&](StringRef param) { os << "typename " << param; }); + os << ">\n"; + } + // Declare the class. os << (isStruct ? "struct" : "class") << ' ' << className << ' '; diff --git a/mlir/lib/TableGen/Dialect.cpp b/mlir/lib/TableGen/Dialect.cpp index 11f972ec7a4a..9f33102702ff 100644 --- a/mlir/lib/TableGen/Dialect.cpp +++ b/mlir/lib/TableGen/Dialect.cpp @@ -110,6 +110,10 @@ bool Dialect::isExtensible() const { return def->getValueAsBit("isExtensible"); } +bool Dialect::usePropertiesForAttributes() const { + return def->getValueAsBit("usePropertiesForAttributes"); +} + bool Dialect::operator==(const Dialect &other) const { return def == other.def; } diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp index dbfc315afae6..aa9560cbc225 100644 --- a/mlir/lib/TableGen/Operator.cpp +++ b/mlir/lib/TableGen/Operator.cpp @@ -69,6 +69,10 @@ std::string Operator::getAdaptorName() const { return std::string(llvm::formatv("{0}Adaptor", getCppClassName())); } +std::string Operator::getGenericAdaptorName() const { + return std::string(llvm::formatv("{0}GenericAdaptor", getCppClassName())); +} + void Operator::assertInvariants() const { // Check that the name of arguments/results/regions/successors don't overlap. DenseMap existingNames; @@ -771,3 +775,11 @@ SmallVector Operator::getGetterNames(StringRef name) const { SmallVector Operator::getSetterNames(StringRef name) const { return getGetterOrSetterNames(/*isGetter=*/false, *this, name); } + +std::string Operator::getRemoverName(StringRef name) const { + return "remove" + convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true); +} + +bool Operator::useCustomPropertiesEncoding() const { + return def.getValueAsBit("useCustomPropertiesEncoding"); +} diff --git a/mlir/lib/TableGen/Property.cpp b/mlir/lib/TableGen/Property.cpp new file mode 100644 index 000000000000..e61d2fd2480f --- /dev/null +++ b/mlir/lib/TableGen/Property.cpp @@ -0,0 +1,71 @@ +//===- Property.cpp - Property wrapper class ----------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// Property wrapper to simplify using TableGen Record defining a MLIR +// Property. +// +//===----------------------------------------------------------------------===// + +#include "mlir/TableGen/Property.h" +#include "mlir/TableGen/Format.h" +#include "mlir/TableGen/Operator.h" +#include "llvm/TableGen/Record.h" + +using namespace mlir; +using namespace mlir::tblgen; + +using llvm::DefInit; +using llvm::Init; +using llvm::Record; +using llvm::StringInit; + +// Returns the initializer's value as string if the given TableGen initializer +// is a code or string initializer. Returns the empty StringRef otherwise. +static StringRef getValueAsString(const Init *init) { + if (const auto *str = dyn_cast(init)) + return str->getValue().trim(); + return {}; +} + +Property::Property(const Record *def) + : Property(getValueAsString(def->getValueInit("storageType")), + getValueAsString(def->getValueInit("interfaceType")), + getValueAsString(def->getValueInit("convertFromStorage")), + getValueAsString(def->getValueInit("assignToStorage")), + getValueAsString(def->getValueInit("convertToAttribute")), + getValueAsString(def->getValueInit("convertFromAttribute")), + getValueAsString(def->getValueInit("readFromMlirBytecode")), + getValueAsString(def->getValueInit("writeToMlirBytecode")), + getValueAsString(def->getValueInit("hashProperty")), + getValueAsString(def->getValueInit("defaultValue"))) { + this->def = def; + assert((def->isSubClassOf("Property") || def->isSubClassOf("Attr")) && + "must be subclass of TableGen 'Property' class"); +} + +Property::Property(const DefInit *init) : Property(init->getDef()) {} + +Property::Property(StringRef storageType, StringRef interfaceType, + StringRef convertFromStorageCall, + StringRef assignToStorageCall, + StringRef convertToAttributeCall, + StringRef convertFromAttributeCall, + StringRef readFromMlirBytecodeCall, + StringRef writeToMlirBytecodeCall, + StringRef hashPropertyCall, StringRef defaultValue) + : storageType(storageType), interfaceType(interfaceType), + convertFromStorageCall(convertFromStorageCall), + assignToStorageCall(assignToStorageCall), + convertToAttributeCall(convertToAttributeCall), + convertFromAttributeCall(convertFromAttributeCall), + readFromMlirBytecodeCall(readFromMlirBytecodeCall), + writeToMlirBytecodeCall(writeToMlirBytecodeCall), + hashPropertyCall(hashPropertyCall), defaultValue(defaultValue) { + if (storageType.empty()) + storageType = "Property"; +} diff --git a/mlir/tools/mlir-tblgen/AttrOrTypeFormatGen.cpp b/mlir/tools/mlir-tblgen/AttrOrTypeFormatGen.cpp index 8321861738d0..205b8c30703d 100644 --- a/mlir/tools/mlir-tblgen/AttrOrTypeFormatGen.cpp +++ b/mlir/tools/mlir-tblgen/AttrOrTypeFormatGen.cpp @@ -200,7 +200,8 @@ private: /// Generate the parser code for a `struct` directive. void genStructParser(StructDirective *el, FmtContext &ctx, MethodBody &os); /// Generate the parser code for a `custom` directive. - void genCustomParser(CustomDirective *el, FmtContext &ctx, MethodBody &os); + void genCustomParser(CustomDirective *el, FmtContext &ctx, MethodBody &os, + bool isOptional = false); /// Generate the parser code for an optional group. void genOptionalGroupParser(OptionalElement *el, FmtContext &ctx, MethodBody &os); @@ -584,7 +585,7 @@ void DefFormat::genStructParser(StructDirective *el, FmtContext &ctx, } void DefFormat::genCustomParser(CustomDirective *el, FmtContext &ctx, - MethodBody &os) { + MethodBody &os, bool isOptional) { os << "{\n"; os.indent(); @@ -597,17 +598,21 @@ void DefFormat::genCustomParser(CustomDirective *el, FmtContext &ctx, os.indent(); for (FormatElement *arg : el->getArguments()) { os << ",\n"; - FormatElement *param; - if (auto *ref = dyn_cast(arg)) { - os << "*"; - param = ref->getArg(); - } else { - param = arg; - } - os << "_result_" << cast(param)->getName(); + if (auto *param = dyn_cast(arg)) + os << "::mlir::detail::unwrapForCustomParse(_result_" << param->getName() + << ")"; + else if (auto *ref = dyn_cast(arg)) + os << "*_result_" << cast(ref->getArg())->getName(); + else + os << tgfmt(cast(arg)->getValue(), &ctx); } os.unindent() << ");\n"; - os << "if (::mlir::failed(odsCustomResult)) return {};\n"; + if (isOptional) { + os << "if (!odsCustomResult) return {};\n"; + os << "if (::mlir::failed(*odsCustomResult)) return ::mlir::failure();\n"; + } else { + os << "if (::mlir::failed(odsCustomResult)) return {};\n"; + } for (FormatElement *arg : el->getArguments()) { if (auto *param = dyn_cast(arg)) { if (param->isOptional()) @@ -616,7 +621,7 @@ void DefFormat::genCustomParser(CustomDirective *el, FmtContext &ctx, os.indent() << tgfmt("$_parser.emitError(odsCustomLoc, ", &ctx) << "\"custom parser failed to parse parameter '" << param->getName() << "'\");\n"; - os << "return {};\n"; + os << "return " << (isOptional ? "::mlir::failure()" : "{}") << ";\n"; os.unindent() << "}\n"; } } @@ -626,10 +631,10 @@ void DefFormat::genCustomParser(CustomDirective *el, FmtContext &ctx, void DefFormat::genOptionalGroupParser(OptionalElement *el, FmtContext &ctx, MethodBody &os) { - ArrayRef elements = - el->getThenElements().drop_front(el->getParseStart()); + ArrayRef thenElements = + el->getThenElements(/*parseable=*/true); - FormatElement *first = elements.front(); + FormatElement *first = thenElements.front(); const auto guardOn = [&](auto params) { os << "if (!("; llvm::interleave( @@ -646,10 +651,21 @@ void DefFormat::genOptionalGroupParser(OptionalElement *el, FmtContext &ctx, os << ") {\n"; } else if (auto *param = dyn_cast(first)) { genVariableParser(param, ctx, os); - guardOn(llvm::makeArrayRef(param)); + guardOn(llvm::ArrayRef(param)); } else if (auto *params = dyn_cast(first)) { genParamsParser(params, ctx, os); guardOn(params->getParams()); + } else if (auto *custom = dyn_cast(first)) { + os << "if (auto result = [&]() -> ::mlir::OptionalParseResult {\n"; + os.indent(); + genCustomParser(custom, ctx, os, /*isOptional=*/true); + os << "return ::mlir::success();\n"; + os.unindent(); + os << "}(); result.has_value() && ::mlir::failed(*result)) {\n"; + os.indent(); + os << "return {};\n"; + os.unindent(); + os << "} else if (result.has_value()) {\n"; } else { auto *strct = cast(first); genStructParser(strct, ctx, os); @@ -657,12 +673,12 @@ void DefFormat::genOptionalGroupParser(OptionalElement *el, FmtContext &ctx, } os.indent(); - // Generate the parsers for the rest of the elements. - for (FormatElement *element : el->getElseElements()) + // Generate the parsers for the rest of the thenElements. + for (FormatElement *element : el->getElseElements(/*parseable=*/true)) genElementParser(element, ctx, os); os.unindent() << "} else {\n"; os.indent(); - for (FormatElement *element : elements.drop_front()) + for (FormatElement *element : thenElements.drop_front()) genElementParser(element, ctx, os); os.unindent() << "}\n"; } @@ -883,9 +899,9 @@ protected: verifyCustomDirectiveArguments(SMLoc loc, ArrayRef arguments) override; /// Verify the elements of an optional group. - LogicalResult - verifyOptionalGroupElements(SMLoc loc, ArrayRef elements, - Optional anchorIndex) override; + LogicalResult verifyOptionalGroupElements(SMLoc loc, + ArrayRef elements, + FormatElement *anchor) override; /// Parse an attribute or type variable. FailureOr parseVariableImpl(SMLoc loc, StringRef name, @@ -955,7 +971,7 @@ LogicalResult DefFormatParser::verifyCustomDirectiveArguments( LogicalResult DefFormatParser::verifyOptionalGroupElements(llvm::SMLoc loc, ArrayRef elements, - Optional anchorIndex) { + FormatElement *anchor) { // `params` and `struct` directives are allowed only if all the contained // parameters are optional. for (FormatElement *el : elements) { @@ -974,13 +990,35 @@ DefFormatParser::verifyOptionalGroupElements(llvm::SMLoc loc, return emitError(loc, "`struct` is only allowed in an optional group " "if all captured parameters are optional"); } + } else if (auto *custom = dyn_cast(el)) { + for (FormatElement *el : custom->getArguments()) { + // If the custom argument is a variable, then it must be optional. + if (auto *param = dyn_cast(el)) + if (!param->isOptional()) + return emitError(loc, + "`custom` is only allowed in an optional group if " + "all captured parameters are optional"); + } } } // The anchor must be a parameter or one of the aforementioned directives. - if (anchorIndex && !isa( - elements[*anchorIndex])) { - return emitError(loc, - "optional group anchor must be a parameter or directive"); + if (anchor) { + if (!isa(anchor)) { + return emitError( + loc, "optional group anchor must be a parameter or directive"); + } + // If the anchor is a custom directive, make sure at least one of its + // arguments is a bound parameter. + if (auto *custom = dyn_cast(anchor)) { + const auto *bound = + llvm::find_if(custom->getArguments(), [](FormatElement *el) { + return isa(el); + }); + if (bound == custom->getArguments().end()) + return emitError(loc, "`custom` directive with no bound parameters " + "cannot be used as optional group anchor"); + } } return success(); } diff --git a/mlir/tools/mlir-tblgen/FormatGen.cpp b/mlir/tools/mlir-tblgen/FormatGen.cpp index 8d08340800c9..b8e28fc1cd8e 100644 --- a/mlir/tools/mlir-tblgen/FormatGen.cpp +++ b/mlir/tools/mlir-tblgen/FormatGen.cpp @@ -283,36 +283,43 @@ FailureOr FormatParser::parseOptionalGroup(Context ctx) { // Parse the child elements for this optional group. std::vector thenElements, elseElements; - Optional anchorIndex; - do { - FailureOr element = parseElement(TopLevelContext); - if (failed(element)) - return failure(); - // Check for an anchor. - if (curToken.is(FormatToken::caret)) { - if (anchorIndex) - return emitError(curToken.getLoc(), "only one element can be marked as " - "the anchor of an optional group"); - anchorIndex = thenElements.size(); - consumeToken(); - } - thenElements.push_back(*element); - } while (!curToken.is(FormatToken::r_paren)); + FormatElement *anchor = nullptr; + auto parseChildElements = + [this, &anchor](std::vector &elements) -> LogicalResult { + do { + FailureOr element = parseElement(TopLevelContext); + if (failed(element)) + return failure(); + // Check for an anchor. + if (curToken.is(FormatToken::caret)) { + if (anchor) { + return emitError(curToken.getLoc(), + "only one element can be marked as the anchor of an " + "optional group"); + } + anchor = *element; + consumeToken(); + } + elements.push_back(*element); + } while (!curToken.is(FormatToken::r_paren)); + return success(); + }; + + // Parse the 'then' elements. If the anchor was found in this group, then the + // optional is not inverted. + if (failed(parseChildElements(thenElements))) + return failure(); consumeToken(); + bool inverted = !anchor; // Parse the `else` elements of this optional group. if (curToken.is(FormatToken::colon)) { consumeToken(); - if (failed( - parseToken(FormatToken::l_paren, - "expected '(' to start else branch of optional group"))) + if (failed(parseToken( + FormatToken::l_paren, + "expected '(' to start else branch of optional group")) || + failed(parseChildElements(elseElements))) return failure(); - do { - FailureOr element = parseElement(TopLevelContext); - if (failed(element)) - return failure(); - elseElements.push_back(*element); - } while (!curToken.is(FormatToken::r_paren)); consumeToken(); } if (failed(parseToken(FormatToken::question, @@ -320,28 +327,31 @@ FailureOr FormatParser::parseOptionalGroup(Context ctx) { return failure(); // The optional group is required to have an anchor. - if (!anchorIndex) + if (!anchor) return emitError(loc, "optional group has no anchor element"); // Verify the child elements. - if (failed(verifyOptionalGroupElements(loc, thenElements, anchorIndex)) || - failed(verifyOptionalGroupElements(loc, elseElements, llvm::None))) + if (failed(verifyOptionalGroupElements(loc, thenElements, anchor)) || + failed(verifyOptionalGroupElements(loc, elseElements, nullptr))) return failure(); // Get the first parsable element. It must be an element that can be // optionally-parsed. - auto parseBegin = llvm::find_if_not(thenElements, [](FormatElement *element) { + auto isWhitespace = [](FormatElement *element) { return isa(element); - }); - if (!isa(*parseBegin)) { + }; + auto thenParseBegin = llvm::find_if_not(thenElements, isWhitespace); + auto elseParseBegin = llvm::find_if_not(elseElements, isWhitespace); + unsigned thenParseStart = std::distance(thenElements.begin(), thenParseBegin); + unsigned elseParseStart = std::distance(elseElements.begin(), elseParseBegin); + + if (!isa(*thenParseBegin)) { return emitError(loc, "first parsable element of an optional group must be " - "a literal or variable"); + "a literal, variable, or custom directive"); } - - unsigned parseStart = std::distance(thenElements.begin(), parseBegin); return create(std::move(thenElements), - std::move(elseElements), *anchorIndex, - parseStart); + std::move(elseElements), thenParseStart, + elseParseStart, anchor, inverted); } FailureOr FormatParser::parseCustomDirective(SMLoc loc, diff --git a/mlir/tools/mlir-tblgen/FormatGen.h b/mlir/tools/mlir-tblgen/FormatGen.h index f180f2da48e8..73c23ae40f5a 100644 --- a/mlir/tools/mlir-tblgen/FormatGen.h +++ b/mlir/tools/mlir-tblgen/FormatGen.h @@ -163,7 +163,7 @@ public: virtual ~FormatElement(); // The top-level kinds of format elements. - enum Kind { Literal, Variable, Whitespace, Directive, Optional }; + enum Kind { Literal, String, Variable, Whitespace, Directive, Optional }; /// Support LLVM-style RTTI. static bool classof(const FormatElement *el) { return true; } @@ -212,6 +212,20 @@ private: StringRef spelling; }; +/// This class represents a raw string that can contain arbitrary C++ code. +class StringElement : public FormatElementBase { +public: + /// Create a string element with the given contents. + explicit StringElement(std::string value) : value(std::move(value)) {} + + /// Get the value of the string element. + StringRef getValue() const { return value; } + +private: + /// The contents of the string. + std::string value; +}; + /// This class represents a variable element. A variable refers to some part of /// the object being parsed, e.g. an attribute or operand on an operation or a /// parameter on an attribute. @@ -272,6 +286,7 @@ public: enum Kind { AttrDict, Custom, + PropDict, FunctionalType, OIList, Operands, @@ -362,34 +377,48 @@ public: /// Create an optional group with the given child elements. OptionalElement(std::vector &&thenElements, std::vector &&elseElements, - unsigned anchorIndex, unsigned parseStart) + unsigned thenParseStart, unsigned elseParseStart, + FormatElement *anchor, bool inverted) : thenElements(std::move(thenElements)), - elseElements(std::move(elseElements)), anchorIndex(anchorIndex), - parseStart(parseStart) {} - - /// Return the `then` elements of the optional group. - ArrayRef getThenElements() const { return thenElements; } + elseElements(std::move(elseElements)), thenParseStart(thenParseStart), + elseParseStart(elseParseStart), anchor(anchor), inverted(inverted) {} + + /// Return the `then` elements of the optional group. Drops the first + /// `thenParseStart` whitespace elements if `parseable` is true. + ArrayRef getThenElements(bool parseable = false) const { + return llvm::ArrayRef(thenElements) + .drop_front(parseable ? thenParseStart : 0); + } - /// Return the `else` elements of the optional group. - ArrayRef getElseElements() const { return elseElements; } + /// Return the `else` elements of the optional group. Drops the first + /// `elseParseStart` whitespace elements if `parseable` is true. + ArrayRef getElseElements(bool parseable = false) const { + return llvm::ArrayRef(elseElements) + .drop_front(parseable ? elseParseStart : 0); + } /// Return the anchor of the optional group. - FormatElement *getAnchor() const { return thenElements[anchorIndex]; } + FormatElement *getAnchor() const { return anchor; } - /// Return the index of the first element to be parsed. - unsigned getParseStart() const { return parseStart; } + /// Return true if the optional group is inverted. + bool isInverted() const { return inverted; } private: /// The child elements emitted when the anchor is present. std::vector thenElements; /// The child elements emitted when the anchor is not present. std::vector elseElements; - /// The index of the anchor element of the optional group within - /// `thenElements`. - unsigned anchorIndex; /// The index of the first element that is parsed in `thenElements`. That is, /// the first non-whitespace element. - unsigned parseStart; + unsigned thenParseStart; + /// The index of the first element that is parsed in `elseElements`. That is, + /// the first non-whitespace element. + unsigned elseParseStart; + /// The anchor element of the optional group. + FormatElement *anchor; + /// Whether the optional group condition is inverted and the anchor element is + /// in the else group. + bool inverted; }; //===----------------------------------------------------------------------===// @@ -478,7 +507,7 @@ protected: virtual LogicalResult verifyOptionalGroupElements(llvm::SMLoc loc, ArrayRef elements, - Optional anchorIndex) = 0; + FormatElement *anchor) = 0; //===--------------------------------------------------------------------===// // Lexer Utilities diff --git a/mlir/tools/mlir-tblgen/OpClass.cpp b/mlir/tools/mlir-tblgen/OpClass.cpp index 3512212272f4..40b688f2b96c 100644 --- a/mlir/tools/mlir-tblgen/OpClass.cpp +++ b/mlir/tools/mlir-tblgen/OpClass.cpp @@ -27,6 +27,11 @@ OpClass::OpClass(StringRef name, StringRef extraClassDeclaration, declare("Op::print"); /// Type alias for the adaptor class. declare("Adaptor", className + "Adaptor"); + declare("GenericAdaptor", + className + "GenericAdaptor") + ->addTemplateParam("RangeT"); + declare( + "FoldAdaptor", "GenericAdaptor<::llvm::ArrayRef<::mlir::Attribute>>"); } void OpClass::finalize() { diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp index 3330fdf3c28a..8d586f4ade88 100644 --- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp +++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp @@ -43,11 +43,15 @@ static const char *const tblgenNamePrefix = "tblgen_"; static const char *const generatedArgName = "odsArg"; static const char *const odsBuilder = "odsBuilder"; static const char *const builderOpState = "odsState"; +static const char *const propertyStorage = "propStorage"; +static const char *const propertyValue = "propValue"; +static const char *const propertyAttr = "propAttr"; +static const char *const propertyDiag = "getDiag"; /// The names of the implicit attributes that contain variadic operand and /// result segment sizes. -static const char *const operandSegmentAttrName = "operand_segment_sizes"; -static const char *const resultSegmentAttrName = "result_segment_sizes"; +static const char *const operandSegmentAttrName = "operandSegmentSizes"; +static const char *const resultSegmentAttrName = "resultSegmentSizes"; /// Code for an Op to lookup an attribute. Uses cached identifiers and subrange /// lookup. @@ -110,6 +114,9 @@ static const char *const adapterSegmentSizeAttrInitCode = R"( assert(odsAttrs && "missing segment size attribute for op"); auto sizeAttr = {0}.cast<::mlir::DenseIntElementsAttr>(); )"; +static const char *const adapterSegmentSizeAttrInitCodeProperties = R"( + ::llvm::ArrayRef sizeAttr = {0}; +)"; /// The code snippet to initialize the sizes for the value range calculation. /// /// {0}: The code to get the attribute. @@ -312,6 +319,30 @@ public: return attrMetadata; } + /// Returns whether to emit a `Properties` struct for this operation or not. + bool hasProperties() const { + if (!op.getProperties().empty()) + return true; + if (!op.getDialect().usePropertiesForAttributes()) + return false; + if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments") || + op.getTrait("::mlir::OpTrait::AttrSizedResultSegments")) + return true; + return llvm::any_of(getAttrMetadata(), + [](const std::pair &it) { + return !it.second.constraint || + !it.second.constraint->isDerivedAttr(); + }); + } + + Optional &getOperandSegmentsSize() { + return operandSegmentsSize; + } + + Optional &getResultSegmentsSize() { + return resultSegmentsSize; + } + private: // Compute the attribute metadata. void computeAttrMetadata(); @@ -323,6 +354,13 @@ private: // The attribute metadata, mapped by name. llvm::MapVector attrMetadata; + + // Property + Optional operandSegmentsSize; + std::string operandSegmentsSizeStorage; + Optional resultSegmentsSize; + std::string resultSegmentsSizeStorage; + // The number of required attributes. unsigned numRequired; }; @@ -382,6 +420,8 @@ void OpOrAdaptorHelper::computeAttrMetadata() { namespace { // Helper class to emit a record into the given output stream. class OpEmitter { + using ConstArgument = + llvm::PointerUnion; public: static void emitDecl(const Operator &op, raw_ostream &os, @@ -406,6 +446,13 @@ private: // Generates the `getOperationName` method for this op. void genOpNameGetter(); + // Generates code to manage the properties, if any! + void genPropertiesSupport(); + + // Generates code to manage the encoding of properties to bytecode. + void + genPropertiesSupportForBytecode(ArrayRef attrOrProperties); + // Generates getters for the attributes. void genAttrGetters(); @@ -630,6 +677,20 @@ static void genNativeTraitAttrVerifier(MethodBody &body, } } +// Return true if a verifier can be emitted for the attribute: it is not a +// derived attribute, it has a predicate, its condition is not empty, and, for +// adaptors, the condition does not reference the op. +static bool canEmitAttrVerifier(Attribute attr, bool isEmittingForOp) { + if (attr.isDerivedAttr()) + return false; + Pred pred = attr.getPredicate(); + if (pred.isNull()) + return false; + std::string condition = pred.getCondition(); + return !condition.empty() && + (!StringRef(condition).contains("$_op") || isEmittingForOp); +} + // Generate attribute verification. If an op instance is not available, then // attribute checks that require one will not be emitted. // @@ -642,9 +703,11 @@ static void genNativeTraitAttrVerifier(MethodBody &body, // that depend on the validity of these attributes, e.g. segment size attributes // and operand or result getters. // 3. Verify the constraints on all present attributes. -static void genAttributeVerifier( - const OpOrAdaptorHelper &emitHelper, FmtContext &ctx, MethodBody &body, - const StaticVerifierFunctionEmitter &staticVerifierEmitter) { +static void +genAttributeVerifier(const OpOrAdaptorHelper &emitHelper, FmtContext &ctx, + MethodBody &body, + const StaticVerifierFunctionEmitter &staticVerifierEmitter, + bool useProperties) { if (emitHelper.getAttrMetadata().empty()) return; @@ -679,7 +742,8 @@ static void genAttributeVerifier( // {0}: Code to get the name of the attribute. // {1}: The emit error prefix. // {2}: The name of the attribute. - const char *const findRequiredAttr = R"(while (true) {{ + const char *const findRequiredAttr = R"( +while (true) {{ if (namedAttrIt == namedAttrRange.end()) return {1}"requires attribute '{2}'"); if (namedAttrIt->getName() == {0}) {{ @@ -702,20 +766,6 @@ static void genAttributeVerifier( break; })"; - // Return true if a verifier can be emitted for the attribute: it is not a - // derived attribute, it has a predicate, its condition is not empty, and, for - // adaptors, the condition does not reference the op. - const auto canEmitVerifier = [&](Attribute attr) { - if (attr.isDerivedAttr()) - return false; - Pred pred = attr.getPredicate(); - if (pred.isNull()) - return false; - std::string condition = pred.getCondition(); - return !condition.empty() && (!StringRef(condition).contains("$_op") || - emitHelper.isEmittingForOp()); - }; - // Emit the verifier for the attribute. const auto emitVerifier = [&](Attribute attr, StringRef attrName, StringRef varName) { @@ -738,58 +788,75 @@ static void genAttributeVerifier( return (tblgenNamePrefix + attrName).str(); }; - body.indent() << formatv("auto namedAttrRange = {0};\n", - emitHelper.getAttrRange()); - body << "auto namedAttrIt = namedAttrRange.begin();\n"; - - // Iterate over the attributes in sorted order. Keep track of the optional - // attributes that may be encountered along the way. - SmallVector optionalAttrs; - for (const std::pair &it : - emitHelper.getAttrMetadata()) { - const AttributeMetadata &metadata = it.second; - if (!metadata.isRequired) { - optionalAttrs.push_back(&metadata); - continue; + body.indent(); + if (useProperties) { + for (const std::pair &it : + emitHelper.getAttrMetadata()) { + const AttributeMetadata &metadata = it.second; + if (metadata.constraint && metadata.constraint->isDerivedAttr()) + continue; + body << formatv( + "auto tblgen_{0} = getProperties().{0}; (void)tblgen_{0};\n", + it.first); + if (metadata.isRequired) + body << formatv( + "if (!tblgen_{0}) return {1}\"requires attribute '{0}'\");\n", + it.first, emitHelper.emitErrorPrefix()); } + } else { + body << formatv("auto namedAttrRange = {0};\n", emitHelper.getAttrRange()); + body << "auto namedAttrIt = namedAttrRange.begin();\n"; + + // Iterate over the attributes in sorted order. Keep track of the optional + // attributes that may be encountered along the way. + SmallVector optionalAttrs; + for (const std::pair &it : + emitHelper.getAttrMetadata()) { + const AttributeMetadata &metadata = it.second; + if (!metadata.isRequired) { + optionalAttrs.push_back(&metadata); + continue; + } - body << formatv("::mlir::Attribute {0};\n", getVarName(it.first)); - for (const AttributeMetadata *optional : optionalAttrs) { - body << formatv("::mlir::Attribute {0};\n", - getVarName(optional->attrName)); - } - body << formatv(findRequiredAttr, emitHelper.getAttrName(it.first), - emitHelper.emitErrorPrefix(), it.first); - for (const AttributeMetadata *optional : optionalAttrs) { - body << formatv(checkOptionalAttr, - emitHelper.getAttrName(optional->attrName), - optional->attrName); - } - body << "\n ++namedAttrIt;\n}\n"; - optionalAttrs.clear(); - } - // Get trailing optional attributes. - if (!optionalAttrs.empty()) { - for (const AttributeMetadata *optional : optionalAttrs) { - body << formatv("::mlir::Attribute {0};\n", - getVarName(optional->attrName)); + body << formatv("::mlir::Attribute {0};\n", getVarName(it.first)); + for (const AttributeMetadata *optional : optionalAttrs) { + body << formatv("::mlir::Attribute {0};\n", + getVarName(optional->attrName)); + } + body << formatv(findRequiredAttr, emitHelper.getAttrName(it.first), + emitHelper.emitErrorPrefix(), it.first); + for (const AttributeMetadata *optional : optionalAttrs) { + body << formatv(checkOptionalAttr, + emitHelper.getAttrName(optional->attrName), + optional->attrName); + } + body << "\n ++namedAttrIt;\n}\n"; + optionalAttrs.clear(); } - body << checkTrailingAttrs; - for (const AttributeMetadata *optional : optionalAttrs) { - body << formatv(checkOptionalAttr, - emitHelper.getAttrName(optional->attrName), - optional->attrName); + // Get trailing optional attributes. + if (!optionalAttrs.empty()) { + for (const AttributeMetadata *optional : optionalAttrs) { + body << formatv("::mlir::Attribute {0};\n", + getVarName(optional->attrName)); + } + body << checkTrailingAttrs; + for (const AttributeMetadata *optional : optionalAttrs) { + body << formatv(checkOptionalAttr, + emitHelper.getAttrName(optional->attrName), + optional->attrName); + } + body << "\n ++namedAttrIt;\n}\n"; } - body << "\n ++namedAttrIt;\n}\n"; } body.unindent(); - // Emit the checks for segment attributes first so that the other constraints - // can call operand and result getters. + // Emit the checks for segment attributes first so that the other + // constraints can call operand and result getters. genNativeTraitAttrVerifier(body, emitHelper); + bool isEmittingForOp = emitHelper.isEmittingForOp(); for (const auto &namedAttr : emitHelper.getOp().getAttributes()) - if (canEmitVerifier(namedAttr.attr)) + if (canEmitAttrVerifier(namedAttr.attr, isEmittingForOp)) emitVerifier(namedAttr.attr, namedAttr.name, getVarName(namedAttr.name)); } @@ -822,6 +889,7 @@ OpEmitter::OpEmitter(const Operator &op, genNamedResultGetters(); genNamedRegionGetters(); genNamedSuccessorGetters(); + genPropertiesSupport(); genAttrGetters(); genAttrSetters(); genOptionalAttrRemovers(); @@ -977,6 +1045,456 @@ static void emitAttrGetterWithReturnType(FmtContext &fctx, << ";\n"; } +void OpEmitter::genPropertiesSupport() { + if (!emitHelper.hasProperties()) + return; + + SmallVector attrOrProperties; + for (const std::pair &it : + emitHelper.getAttrMetadata()) { + if (!it.second.constraint || !it.second.constraint->isDerivedAttr()) + attrOrProperties.push_back(&it.second); + } + for (const NamedProperty &prop : op.getProperties()) + attrOrProperties.push_back(&prop); + if (emitHelper.getOperandSegmentsSize()) + attrOrProperties.push_back(&emitHelper.getOperandSegmentsSize().value()); + if (emitHelper.getResultSegmentsSize()) + attrOrProperties.push_back(&emitHelper.getResultSegmentsSize().value()); + if (attrOrProperties.empty()) + return; + auto &setPropMethod = + opClass + .addStaticMethod( + "::mlir::LogicalResult", "setPropertiesFromAttr", + MethodParameter("Properties &", "prop"), + MethodParameter("::mlir::Attribute", "attr"), + MethodParameter( + "::llvm::function_ref<::mlir::InFlightDiagnostic &()>", + "getDiag")) + ->body(); + auto &getPropMethod = + opClass + .addStaticMethod("::mlir::Attribute", "getPropertiesAsAttr", + MethodParameter("::mlir::MLIRContext *", "ctx"), + MethodParameter("const Properties &", "prop")) + ->body(); + auto &hashMethod = + opClass + .addStaticMethod("llvm::hash_code", "computePropertiesHash", + MethodParameter("const Properties &", "prop")) + ->body(); + auto &getInherentAttrMethod = + opClass + .addStaticMethod("Optional", "getInherentAttr", + MethodParameter("::mlir::MLIRContext *", "ctx"), + MethodParameter("const Properties &", "prop"), + MethodParameter("llvm::StringRef", "name")) + ->body(); + auto &setInherentAttrMethod = + opClass + .addStaticMethod("void", "setInherentAttr", + MethodParameter("Properties &", "prop"), + MethodParameter("llvm::StringRef", "name"), + MethodParameter("mlir::Attribute", "value")) + ->body(); + auto &populateInherentAttrsMethod = + opClass + .addStaticMethod("void", "populateInherentAttrs", + MethodParameter("::mlir::MLIRContext *", "ctx"), + MethodParameter("const Properties &", "prop"), + MethodParameter("::mlir::NamedAttrList &", "attrs")) + ->body(); + auto &verifyInherentAttrsMethod = + opClass + .addStaticMethod( + "::mlir::LogicalResult", "verifyInherentAttrs", + MethodParameter("::mlir::OperationName", "opName"), + MethodParameter("::mlir::NamedAttrList &", "attrs"), + MethodParameter( + "llvm::function_ref<::mlir::InFlightDiagnostic()>", + "getDiag")) + ->body(); + + opClass.declare("Properties", "FoldAdaptor::Properties"); + + // Convert the property to the attribute form. + + setPropMethod << R"decl( + ::mlir::DictionaryAttr dict = ::llvm::dyn_cast<::mlir::DictionaryAttr>(attr); + if (!dict) { + getDiag() << "expected DictionaryAttr to set properties"; + return ::mlir::failure(); + } + )decl"; + // TODO: properties might be optional as well. + const char *propFromAttrFmt = R"decl(; + {{ + auto setFromAttr = [] (auto &propStorage, ::mlir::Attribute propAttr, + ::llvm::function_ref<::mlir::InFlightDiagnostic &()> getDiag) {{ + {0}; + }; + {2}; + if (!attr) {{ + getDiag() << "expected key entry for {1} in DictionaryAttr to set " + "Properties."; + return ::mlir::failure(); + } + if (::mlir::failed(setFromAttr(prop.{1}, attr, getDiag))) + return ::mlir::failure(); + } +)decl"; + + for (const auto &attrOrProp : attrOrProperties) { + if (const auto *namedProperty = + attrOrProp.dyn_cast()) { + StringRef name = namedProperty->name; + auto &prop = namedProperty->prop; + FmtContext fctx; + + std::string getAttr; + llvm::raw_string_ostream os(getAttr); + os << " auto attr = dict.get(\"" << name << "\");"; + if (name == operandSegmentAttrName) { + // Backward compat for now, TODO: Remove at some point. + os << " if (!attr) attr = dict.get(\"operand_segment_sizes\");"; + } + if (name == resultSegmentAttrName) { + // Backward compat for now, TODO: Remove at some point. + os << " if (!attr) attr = dict.get(\"result_segment_sizes\");"; + } + os.flush(); + + setPropMethod << formatv(propFromAttrFmt, + tgfmt(prop.getConvertFromAttributeCall(), + &fctx.addSubst("_attr", propertyAttr) + .addSubst("_storage", propertyStorage) + .addSubst("_diag", propertyDiag)), + name, getAttr); + + } else { + const auto *namedAttr = + attrOrProp.dyn_cast(); + StringRef name = namedAttr->attrName; + std::string getAttr; + llvm::raw_string_ostream os(getAttr); + os << " auto attr = dict.get(\"" << name << "\");"; + if (name == operandSegmentAttrName) { + // Backward compat for now + os << " if (!attr) attr = dict.get(\"operand_segment_sizes\");"; + } + if (name == resultSegmentAttrName) { + // Backward compat for now + os << " if (!attr) attr = dict.get(\"result_segment_sizes\");"; + } + os.flush(); + + setPropMethod << formatv(R"decl( + {{ + auto &propStorage = prop.{0}; + {2} + if (attr || /*isRequired=*/{1}) {{ + if (!attr) {{ + getDiag() << "expected key entry for {0} in DictionaryAttr to set " + "Properties."; + return ::mlir::failure(); + } + auto convertedAttr = ::llvm::dyn_cast>(attr); + if (convertedAttr) {{ + propStorage = convertedAttr; + } else {{ + getDiag() << "Invalid attribute `{0}` in property conversion: " << attr; + return ::mlir::failure(); + } + } + } +)decl", + name, namedAttr->isRequired, getAttr); + } + } + setPropMethod << " return ::mlir::success();\n"; + + // Convert the attribute form to the property. + + getPropMethod << " ::mlir::SmallVector<::mlir::NamedAttribute> attrs;\n" + << " ::mlir::Builder odsBuilder{ctx};\n"; + const char *propToAttrFmt = R"decl( + { + const auto &propStorage = prop.{0}; + attrs.push_back(odsBuilder.getNamedAttr("{0}", + {1})); + } +)decl"; + for (const auto &attrOrProp : attrOrProperties) { + if (const auto *namedProperty = + attrOrProp.dyn_cast()) { + StringRef name = namedProperty->name; + auto &prop = namedProperty->prop; + FmtContext fctx; + getPropMethod << formatv( + propToAttrFmt, name, + tgfmt(prop.getConvertToAttributeCall(), + &fctx.addSubst("_ctxt", "ctx") + .addSubst("_storage", propertyStorage))); + continue; + } + const auto *namedAttr = + attrOrProp.dyn_cast(); + StringRef name = namedAttr->attrName; + getPropMethod << formatv(R"decl( + {{ + const auto &propStorage = prop.{0}; + if (propStorage) + attrs.push_back(odsBuilder.getNamedAttr("{0}", + propStorage)); + } +)decl", + name); + } + getPropMethod << R"decl( + if (!attrs.empty()) + return odsBuilder.getDictionaryAttr(attrs); + return {}; +)decl"; + + // Hashing for the property + + const char *propHashFmt = R"decl( + auto hash_{0} = [] (const auto &propStorage) -> llvm::hash_code { + return {1}; + }; +)decl"; + for (const auto &attrOrProp : attrOrProperties) { + if (const auto *namedProperty = + attrOrProp.dyn_cast()) { + StringRef name = namedProperty->name; + auto &prop = namedProperty->prop; + FmtContext fctx; + hashMethod << formatv(propHashFmt, name, + tgfmt(prop.getHashPropertyCall(), + &fctx.addSubst("_storage", propertyStorage))); + } + } + hashMethod << " return llvm::hash_combine("; + llvm::interleaveComma( + attrOrProperties, hashMethod, [&](const ConstArgument &attrOrProp) { + if (const auto *namedProperty = + attrOrProp.dyn_cast()) { + hashMethod << "\n hash_" << namedProperty->name << "(prop." + << namedProperty->name << ")"; + return; + } + const auto *namedAttr = + attrOrProp.dyn_cast(); + StringRef name = namedAttr->attrName; + hashMethod << "\n llvm::hash_value(prop." << name + << ".getAsOpaquePointer())"; + }); + hashMethod << ");\n"; + + const char *getInherentAttrMethodFmt = R"decl( + if (name == "{0}") + return prop.{0}; +)decl"; + const char *setInherentAttrMethodFmt = R"decl( + if (name == "{0}") {{ + prop.{0} = ::llvm::dyn_cast_or_null>(value); + return; + } +)decl"; + const char *populateInherentAttrsMethodFmt = R"decl( + if (prop.{0}) attrs.append("{0}", prop.{0}); +)decl"; + for (const auto &attrOrProp : attrOrProperties) { + if (const auto *namedAttr = + attrOrProp.dyn_cast()) { + StringRef name = namedAttr->attrName; + getInherentAttrMethod << formatv(getInherentAttrMethodFmt, name); + setInherentAttrMethod << formatv(setInherentAttrMethodFmt, name); + populateInherentAttrsMethod + << formatv(populateInherentAttrsMethodFmt, name); + continue; + } + // The ODS segment size property is "special": we expose it as an attribute + // even though it is a native property. + const auto *namedProperty = cast(attrOrProp); + StringRef name = namedProperty->name; + if (name != operandSegmentAttrName && name != resultSegmentAttrName) + continue; + auto &prop = namedProperty->prop; + FmtContext fctx; + fctx.addSubst("_ctxt", "ctx"); + fctx.addSubst("_storage", Twine("prop.") + name); + if (name == operandSegmentAttrName) { + getInherentAttrMethod + << formatv(" if (name == \"operand_segment_sizes\" || name == " + "\"{0}\") return ", + operandSegmentAttrName); + } else { + getInherentAttrMethod + << formatv(" if (name == \"result_segment_sizes\" || name == " + "\"{0}\") return ", + resultSegmentAttrName); + } + getInherentAttrMethod << tgfmt(prop.getConvertToAttributeCall(), &fctx) + << ";\n"; + + if (name == operandSegmentAttrName) { + setInherentAttrMethod + << formatv(" if (name == \"operand_segment_sizes\" || name == " + "\"{0}\") {{", + operandSegmentAttrName); + } else { + setInherentAttrMethod + << formatv(" if (name == \"result_segment_sizes\" || name == " + "\"{0}\") {{", + resultSegmentAttrName); + } + setInherentAttrMethod << formatv(R"decl( + auto arrAttr = ::llvm::dyn_cast_or_null<::mlir::DenseI32ArrayAttr>(value); + if (!arrAttr) return; + if (arrAttr.size() != sizeof(prop.{0}) / sizeof(int32_t)) + return; + llvm::copy(arrAttr.asArrayRef(), prop.{0}.begin()); + return; + } +)decl", + name); + if (name == operandSegmentAttrName) { + populateInherentAttrsMethod + << formatv(" attrs.append(\"{0}\", {1});\n", operandSegmentAttrName, + tgfmt(prop.getConvertToAttributeCall(), &fctx)); + } else { + populateInherentAttrsMethod + << formatv(" attrs.append(\"{0}\", {1});\n", resultSegmentAttrName, + tgfmt(prop.getConvertToAttributeCall(), &fctx)); + } + } + getInherentAttrMethod << " return llvm::None;\n"; + + // Emit the verifiers method for backward compatibility with the generic + // syntax. This method verifies the constraint on the properties attributes + // before they are set, since dyn_cast<> will silently omit failures. + for (const auto &attrOrProp : attrOrProperties) { + const auto *namedAttr = + attrOrProp.dyn_cast(); + if (!namedAttr || !namedAttr->constraint) + continue; + Attribute attr = *namedAttr->constraint; + Optional constraintFn = + staticVerifierEmitter.getAttrConstraintFn(attr); + if (!constraintFn) + continue; + if (canEmitAttrVerifier(attr, + /*isEmittingForOp=*/false)) { + std::string name = op.getGetterName(namedAttr->attrName); + verifyInherentAttrsMethod + << formatv(R"( + {{ + ::mlir::Attribute attr = attrs.get({0}AttrName(opName)); + if (attr && ::mlir::failed({1}(attr, "{2}", getDiag))) + return ::mlir::failure(); + } +)", + name, constraintFn, namedAttr->attrName); + } + } + verifyInherentAttrsMethod << " return ::mlir::success();"; + + // Generate methods to interact with bytecode. + genPropertiesSupportForBytecode(attrOrProperties); +} + +void OpEmitter::genPropertiesSupportForBytecode( + ArrayRef attrOrProperties) { + if (op.useCustomPropertiesEncoding()) { + opClass.declareStaticMethod( + "::mlir::LogicalResult", "readProperties", + MethodParameter("::mlir::DialectBytecodeReader &", "reader"), + MethodParameter("::mlir::OperationState &", "state")); + opClass.declareMethod( + "void", "writeProperties", + MethodParameter("::mlir::DialectBytecodeWriter &", "writer")); + return; + } + + auto &readPropertiesMethod = + opClass + .addStaticMethod( + "::mlir::LogicalResult", "readProperties", + MethodParameter("::mlir::DialectBytecodeReader &", "reader"), + MethodParameter("::mlir::OperationState &", "state")) + ->body(); + + auto &writePropertiesMethod = + opClass + .addMethod( + "void", "writeProperties", + MethodParameter("::mlir::DialectBytecodeWriter &", "writer")) + ->body(); + + // Populate bytecode serialization logic. + readPropertiesMethod + << " auto &prop = state.getOrAddProperties(); (void)prop;"; + writePropertiesMethod << " auto &prop = getProperties(); (void)prop;\n"; + for (const auto &attrOrProp : attrOrProperties) { + if (const auto *namedProperty = + attrOrProp.dyn_cast()) { + StringRef name = namedProperty->name; + FmtContext fctx; + fctx.addSubst("_reader", "reader") + .addSubst("_writer", "writer") + .addSubst("_storage", propertyStorage) + .addSubst("_ctxt", "this->getContext()"); + readPropertiesMethod << formatv( + R"( + {{ + auto &propStorage = prop.{0}; + auto readProp = [&]() { + {1}; + return ::mlir::success(); + }; + if (::mlir::failed(readProp())) + return ::mlir::failure(); + } +)", + name, + tgfmt(namedProperty->prop.getReadFromMlirBytecodeCall(), &fctx)); + writePropertiesMethod << formatv( + R"( + {{ + auto &propStorage = prop.{0}; + {1}; + } +)", + name, tgfmt(namedProperty->prop.getWriteToMlirBytecodeCall(), &fctx)); + continue; + } + const auto *namedAttr = attrOrProp.dyn_cast(); + StringRef name = namedAttr->attrName; + if (namedAttr->isRequired) { + readPropertiesMethod << formatv(R"( + if (::mlir::failed(reader.readAttribute(prop.{0}))) + return ::mlir::failure(); +)", + name); + writePropertiesMethod + << formatv(" writer.writeAttribute(prop.{0});\n", name); + } else { + readPropertiesMethod << formatv(R"( + if (::mlir::failed(reader.readOptionalAttribute(prop.{0}))) + return ::mlir::failure(); +)", + name); + writePropertiesMethod << formatv(R"( + writer.writeOptionalAttribute(prop.{0}); +)", + name); + } + } + readPropertiesMethod << " return ::mlir::success();"; +} + void OpEmitter::genAttrGetters() { FmtContext fctx; fctx.withBuilder("::mlir::Builder((*this)->getContext())"); @@ -1110,32 +1628,50 @@ void OpEmitter::genAttrSetters() { void OpEmitter::genOptionalAttrRemovers() { // Generate methods for removing optional attributes, instead of having to // use the string interface. Enables better compile time verification. - auto emitRemoveAttr = [&](StringRef name) { + auto emitRemoveAttr = [&](StringRef name, bool useProperties) { auto upperInitial = name.take_front().upper(); - auto suffix = name.drop_front(); auto *method = opClass.addMethod("::mlir::Attribute", - "remove" + upperInitial + suffix + "Attr"); + op.getRemoverName(name) + "Attr"); if (!method) return; - method->body() << formatv(" return (*this)->removeAttr({0}AttrName());", + if (useProperties) { + method->body() << formatv(R"( + auto &attr = getProperties().{0}; + attr = {{}; + return attr; +)", + name); + return; + } + method->body() << formatv("return (*this)->removeAttr({0}AttrName());", op.getGetterName(name)); }; for (const NamedAttribute &namedAttr : op.getAttributes()) if (namedAttr.attr.isOptional()) - emitRemoveAttr(namedAttr.name); + emitRemoveAttr(namedAttr.name, + op.getDialect().usePropertiesForAttributes()); } // Generates the code to compute the start and end index of an operand or result // range. template -static void -generateValueRangeStartAndEnd(Class &opClass, StringRef methodName, - int numVariadic, int numNonVariadic, - StringRef rangeSizeCall, bool hasAttrSegmentSize, - StringRef sizeAttrInit, RangeT &&odsValues) { +static void generateValueRangeStartAndEnd( + Class &opClass, bool isGenericAdaptorBase, StringRef methodName, + int numVariadic, int numNonVariadic, StringRef rangeSizeCall, + bool hasAttrSegmentSize, StringRef sizeAttrInit, RangeT &&odsValues) { + + SmallVector parameters{MethodParameter("unsigned", "index")}; + if (isGenericAdaptorBase) { + parameters.emplace_back("unsigned", "odsOperandsSize"); + // The range size is passed per parameter for generic adaptor bases as + // using the rangeSizeCall would require the operands, which are not + // accessible in the base class. + rangeSizeCall = "odsOperandsSize"; + } + auto *method = opClass.addMethod("std::pair", methodName, - MethodParameter("unsigned", "index")); + parameters); if (!method) return; auto &body = method->body(); @@ -1157,6 +1693,24 @@ generateValueRangeStartAndEnd(Class &opClass, StringRef methodName, } } +static std::string generateTypeForGetter(const NamedTypeConstraint &value) { + std::string str = "::mlir::Value"; + /// If the CPPClassName is not a fully qualified type. Uses of types + /// across Dialect fail because they are not in the correct namespace. So we + /// dont generate TypedValue unless the type is fully qualified. + /// getCPPClassName doesn't return the fully qualified path for + /// `mlir::pdl::OperationType` see + /// https://github.com/llvm/llvm-project/issues/57279. + /// Adaptor will have values that are not from the type of their operation and + /// this is expected, so we dont generate TypedValue for Adaptor + if (value.constraint.getCPPClassName() != "::mlir::Type" && + StringRef(value.constraint.getCPPClassName()).startswith("::")) + str = llvm::formatv("::mlir::TypedValue<{0}>", + value.constraint.getCPPClassName()) + .str(); + return str; +} + // Generates the named operand getter methods for the given Operator `op` and // puts them in `opClass`. Uses `rangeType` as the return type of getters that // return a range of operands (individual operands are `Value ` and each @@ -1168,12 +1722,12 @@ generateValueRangeStartAndEnd(Class &opClass, StringRef methodName, // "{0}" marker in the pattern. Note that the pattern should work for any kind // of ops, in particular for one-operand ops that may not have the // `getOperand(unsigned)` method. -static void generateNamedOperandGetters(const Operator &op, Class &opClass, - bool isAdaptor, StringRef sizeAttrInit, - StringRef rangeType, - StringRef rangeBeginCall, - StringRef rangeSizeCall, - StringRef getOperandCallPattern) { +static void +generateNamedOperandGetters(const Operator &op, Class &opClass, + Class *genericAdaptorBase, StringRef sizeAttrInit, + StringRef rangeType, StringRef rangeElementType, + StringRef rangeBeginCall, StringRef rangeSizeCall, + StringRef getOperandCallPattern) { const int numOperands = op.getNumOperands(); const int numVariadicOperands = op.getNumVariableLengthOperands(); const int numNormalOperands = numOperands - numVariadicOperands; @@ -1201,10 +1755,32 @@ static void generateNamedOperandGetters(const Operator &op, Class &opClass, // First emit a few "sink" getter methods upon which we layer all nicer named // getter methods. - generateValueRangeStartAndEnd(opClass, "getODSOperandIndexAndLength", - numVariadicOperands, numNormalOperands, - rangeSizeCall, attrSizedOperands, sizeAttrInit, - const_cast(op).getOperands()); + // If generating for an adaptor, the method is put into the non-templated + // generic base class, to not require being defined in the header. + // Since the operand size can't be determined from the base class however, + // it has to be passed as an additional argument. The trampoline below + // generates the function with the same signature as the Op in the generic + // adaptor. + bool isGenericAdaptorBase = genericAdaptorBase != nullptr; + generateValueRangeStartAndEnd( + /*opClass=*/isGenericAdaptorBase ? *genericAdaptorBase : opClass, + isGenericAdaptorBase, + /*methodName=*/"getODSOperandIndexAndLength", numVariadicOperands, + numNormalOperands, rangeSizeCall, attrSizedOperands, sizeAttrInit, + const_cast(op).getOperands()); + if (isGenericAdaptorBase) { + // Generate trampoline for calling 'getODSOperandIndexAndLength' with just + // the index. This just calls the implementation in the base class but + // passes the operand size as parameter. + Method *method = opClass.addMethod("std::pair", + "getODSOperandIndexAndLength", + MethodParameter("unsigned", "index")); + ERROR_IF_PRUNED(method, "getODSOperandIndexAndLength", op); + MethodBody &body = method->body(); + + body.indent() << formatv( + "return Base::getODSOperandIndexAndLength(index, {0});", rangeSizeCall); + } auto *m = opClass.addMethod(rangeType, "getODSOperands", MethodParameter("unsigned", "index")); @@ -1219,38 +1795,48 @@ static void generateNamedOperandGetters(const Operator &op, Class &opClass, const auto &operand = op.getOperand(i); if (operand.name.empty()) continue; - for (StringRef name : op.getGetterNames(operand.name)) { - if (operand.isOptional()) { - m = opClass.addMethod("::mlir::Value", name); - ERROR_IF_PRUNED(m, name, op); - m->body() << " auto operands = getODSOperands(" << i << ");\n" - << " return operands.empty() ? ::mlir::Value() : " - "*operands.begin();"; - } else if (operand.isVariadicOfVariadic()) { - std::string segmentAttr = op.getGetterName( - operand.constraint.getVariadicOfVariadicSegmentSizeAttr()); - if (isAdaptor) { - m = opClass.addMethod("::llvm::SmallVector<::mlir::ValueRange>", - name); - ERROR_IF_PRUNED(m, name, op); - m->body() << llvm::formatv(variadicOfVariadicAdaptorCalcCode, - segmentAttr, i); - continue; - } - - m = opClass.addMethod("::mlir::OperandRangeRange", name); - ERROR_IF_PRUNED(m, name, op); - m->body() << " return getODSOperands(" << i << ").split(" - << segmentAttr << "Attr());"; - } else if (operand.isVariadic()) { - m = opClass.addMethod(rangeType, name); - ERROR_IF_PRUNED(m, name, op); - m->body() << " return getODSOperands(" << i << ");"; - } else { - m = opClass.addMethod("::mlir::Value", name); + std::string name = op.getGetterName(operand.name); + if (operand.isOptional()) { + m = opClass.addMethod(isGenericAdaptorBase + ? rangeElementType + : generateTypeForGetter(operand), + name); + ERROR_IF_PRUNED(m, name, op); + m->body().indent() << formatv("auto operands = getODSOperands({0});\n" + "return operands.empty() ? {1}{{} : ", + i, m->getReturnType()); + if (!isGenericAdaptorBase) + m->body() << llvm::formatv("::llvm::cast<{0}>", m->getReturnType()); + m->body() << "(*operands.begin());"; + } else if (operand.isVariadicOfVariadic()) { + std::string segmentAttr = op.getGetterName( + operand.constraint.getVariadicOfVariadicSegmentSizeAttr()); + if (genericAdaptorBase) { + m = opClass.addMethod("::llvm::SmallVector<" + rangeType + ">", name); ERROR_IF_PRUNED(m, name, op); - m->body() << " return *getODSOperands(" << i << ").begin();"; + m->body() << llvm::formatv(variadicOfVariadicAdaptorCalcCode, + segmentAttr, i, rangeType); + continue; } + + m = opClass.addMethod("::mlir::OperandRangeRange", name); + ERROR_IF_PRUNED(m, name, op); + m->body() << " return getODSOperands(" << i << ").split(" << segmentAttr + << "Attr());"; + } else if (operand.isVariadic()) { + m = opClass.addMethod(rangeType, name); + ERROR_IF_PRUNED(m, name, op); + m->body() << " return getODSOperands(" << i << ");"; + } else { + m = opClass.addMethod(isGenericAdaptorBase + ? rangeElementType + : generateTypeForGetter(operand), + name); + ERROR_IF_PRUNED(m, name, op); + m->body().indent() << "return "; + if (!isGenericAdaptorBase) + m->body() << llvm::formatv("::llvm::cast<{0}>", m->getReturnType()); + m->body() << llvm::formatv("(*getODSOperands({0}).begin());", i); } } } @@ -1260,15 +1846,21 @@ void OpEmitter::genNamedOperandGetters() { // array. std::string attrSizeInitCode; if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments")) { - attrSizeInitCode = formatv(opSegmentSizeAttrInitCode, - emitHelper.getAttr(operandSegmentAttrName)); + if (op.getDialect().usePropertiesForAttributes()) + attrSizeInitCode = formatv(adapterSegmentSizeAttrInitCodeProperties, + "getProperties().operandSegmentSizes"); + + else + attrSizeInitCode = formatv(opSegmentSizeAttrInitCode, + emitHelper.getAttr(operandSegmentAttrName)); } generateNamedOperandGetters( op, opClass, - /*isAdaptor=*/false, + /*genericAdaptorBase=*/nullptr, /*sizeAttrInit=*/attrSizeInitCode, /*rangeType=*/"::mlir::Operation::operand_range", + /*rangeElementType=*/"::mlir::Value", /*rangeBeginCall=*/"getOperation()->operand_begin()", /*rangeSizeCall=*/"getOperation()->getNumOperands()", /*getOperandCallPattern=*/"getOperation()->getOperand({0})"); @@ -1348,14 +1940,19 @@ void OpEmitter::genNamedResultGetters() { // Build the initializer string for the result segment size attribute. std::string attrSizeInitCode; if (attrSizedResults) { - attrSizeInitCode = formatv(opSegmentSizeAttrInitCode, - emitHelper.getAttr(resultSegmentAttrName)); + if (op.getDialect().usePropertiesForAttributes()) + attrSizeInitCode = formatv(adapterSegmentSizeAttrInitCodeProperties, + "getProperties().resultSegmentSizes"); + + else + attrSizeInitCode = formatv(opSegmentSizeAttrInitCode, + emitHelper.getAttr(resultSegmentAttrName)); } generateValueRangeStartAndEnd( - opClass, "getODSResultIndexAndLength", numVariadicResults, - numNormalResults, "getOperation()->getNumResults()", attrSizedResults, - attrSizeInitCode, op.getResults()); + opClass, /*isGenericAdaptorBase=*/false, "getODSResultIndexAndLength", + numVariadicResults, numNormalResults, "getOperation()->getNumResults()", + attrSizedResults, attrSizeInitCode, op.getResults()); auto *m = opClass.addMethod("::mlir::Operation::result_range", "getODSResults", @@ -2473,9 +3070,11 @@ void OpEmitter::genVerifier() { opClass.addMethod("::mlir::LogicalResult", "verifyInvariantsImpl"); ERROR_IF_PRUNED(implMethod, "verifyInvariantsImpl", op); auto &implBody = implMethod->body(); + bool useProperties = emitHelper.hasProperties(); populateSubstitutions(emitHelper, verifyCtx); - genAttributeVerifier(emitHelper, verifyCtx, implBody, staticVerifierEmitter); + genAttributeVerifier(emitHelper, verifyCtx, implBody, staticVerifierEmitter, + useProperties); genOperandResultVerifier(implBody, op.getOperands(), "operand"); genOperandResultVerifier(implBody, op.getResults(), "result"); @@ -2829,7 +3428,9 @@ private: // The operation for which to emit an adaptor. const Operator &op; - // The generated adaptor class. + // The generated adaptor classes. + Class genericAdaptorBase; + Class genericAdaptor; Class adaptor; // The emitter containing all of the locally emitted verification functions. @@ -2843,90 +3444,280 @@ private: OpOperandAdaptorEmitter::OpOperandAdaptorEmitter( const Operator &op, const StaticVerifierFunctionEmitter &staticVerifierEmitter) - : op(op), adaptor(op.getAdaptorName()), + : op(op), genericAdaptorBase(op.getGenericAdaptorName() + "Base"), + genericAdaptor(op.getGenericAdaptorName()), adaptor(op.getAdaptorName()), staticVerifierEmitter(staticVerifierEmitter), emitHelper(op, /*emitForOp=*/false) { - adaptor.addField("::mlir::ValueRange", "odsOperands"); - adaptor.addField("::mlir::DictionaryAttr", "odsAttrs"); - adaptor.addField("::mlir::RegionRange", "odsRegions"); - adaptor.addField("::llvm::Optional<::mlir::OperationName>", "odsOpName"); + + genericAdaptorBase.declare(Visibility::Public); + bool useProperties = emitHelper.hasProperties(); + if (useProperties) { + // Define the properties struct with multiple members. + using ConstArgument = + llvm::PointerUnion; + SmallVector attrOrProperties; + for (const std::pair &it : + emitHelper.getAttrMetadata()) { + if (!it.second.constraint || !it.second.constraint->isDerivedAttr()) + attrOrProperties.push_back(&it.second); + } + for (const NamedProperty &prop : op.getProperties()) + attrOrProperties.push_back(&prop); + if (emitHelper.getOperandSegmentsSize()) + attrOrProperties.push_back(&emitHelper.getOperandSegmentsSize().value()); + if (emitHelper.getResultSegmentsSize()) + attrOrProperties.push_back(&emitHelper.getResultSegmentsSize().value()); + assert(!attrOrProperties.empty()); + std::string declarations = " struct Properties {\n"; + llvm::raw_string_ostream os(declarations); + std::string comparator = + " bool operator==(const Properties &rhs) const {\n" + " return \n"; + llvm::raw_string_ostream comparatorOs(comparator); + for (const auto &attrOrProp : attrOrProperties) { + if (const auto *namedProperty = + attrOrProp.dyn_cast()) { + StringRef name = namedProperty->name; + if (name.empty()) + report_fatal_error("missing name for property"); + std::string camelName = + convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true); + auto &prop = namedProperty->prop; + // Generate the data member using the storage type. + os << " using " << name << "Ty = " << prop.getStorageType() << ";\n" + << " " << name << "Ty " << name; + if (prop.hasDefaultValue()) + os << " = " << prop.getDefaultValue(); + comparatorOs << " rhs." << name << " == this->" << name + << " &&\n"; + // Emit accessors using the interface type. + const char *accessorFmt = R"decl(; + {0} get{1}() { + auto &propStorage = this->{2}; + return {3}; + } + void set{1}(const {0} &propValue) { + auto &propStorage = this->{2}; + {4}; + } +)decl"; + FmtContext fctx; + os << formatv(accessorFmt, prop.getInterfaceType(), camelName, name, + tgfmt(prop.getConvertFromStorageCall(), + &fctx.addSubst("_storage", propertyStorage)), + tgfmt(prop.getAssignToStorageCall(), + &fctx.addSubst("_value", propertyValue) + .addSubst("_storage", propertyStorage))); + continue; + } + const auto *namedAttr = + attrOrProp.dyn_cast(); + const Attribute *attr = nullptr; + if (namedAttr->constraint) + attr = &*namedAttr->constraint; + StringRef name = namedAttr->attrName; + if (name.empty()) + report_fatal_error("missing name for property attr"); + std::string camelName = + convertToCamelFromSnakeCase(name, /*capitalizeFirst=*/true); + // Generate the data member using the storage type. + StringRef storageType; + if (attr) { + storageType = attr->getStorageType(); + } else { + if (name != operandSegmentAttrName && name != resultSegmentAttrName) { + report_fatal_error("unexpected AttributeMetadata"); + } + // TODO: update to use native integers. + storageType = "::mlir::DenseI32ArrayAttr"; + } + os << " using " << name << "Ty = " << storageType << ";\n" + << " " << name << "Ty " << name << ";\n"; + comparatorOs << " rhs." << name << " == this->" << name << " &&\n"; + + // Emit accessors using the interface type. + if (attr) { + os << formatv(" auto get{0}() {{\n auto &propStorage = this->{1};\n" + " return ::llvm::{2}<{3}>(propStorage);\n }\n" + " void set{0}(const {3} &propValue) {{\n this->{1} = propValue;\n }\n", + camelName, name, attr->isOptional() || attr->hasDefaultValue() + ? "dyn_cast_or_null" : "cast", storageType); + } + } + comparatorOs << " true;\n }\n" + " bool operator!=(const Properties &rhs) const {\n" + " return !(*this == rhs);\n" + " }\n"; + comparatorOs.flush(); + // os << comparator; + os << "};\n"; + os.flush(); + + genericAdaptorBase.declare(std::move(declarations)); + } + genericAdaptorBase.declare(Visibility::Protected); + genericAdaptorBase.declare("::mlir::DictionaryAttr", "odsAttrs"); + genericAdaptorBase.declare("::llvm::Optional<::mlir::OperationName>", + "odsOpName"); + if (useProperties) + genericAdaptorBase.declare("Properties", "properties"); + genericAdaptorBase.declare("::mlir::RegionRange", "odsRegions"); + + genericAdaptor.addTemplateParam("RangeT"); + genericAdaptor.addField("RangeT", "odsOperands"); + genericAdaptor.addParent( + ParentClass("detail::" + genericAdaptorBase.getClassName())); + genericAdaptor.declare( + "ValueT", "::llvm::detail::ValueOfRange"); + genericAdaptor.declare( + "Base", "detail::" + genericAdaptorBase.getClassName()); const auto *attrSizedOperands = - op.getTrait("::m::OpTrait::AttrSizedOperandSegments"); + op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments"); { SmallVector paramList; - paramList.emplace_back("::mlir::ValueRange", "values"); paramList.emplace_back("::mlir::DictionaryAttr", "attrs", attrSizedOperands ? "" : "nullptr"); + if (useProperties) + paramList.emplace_back("const Properties &", "properties", "{}"); + else + paramList.emplace_back("const ::mlir::EmptyProperties &", "properties", + "{}"); paramList.emplace_back("::mlir::RegionRange", "regions", "{}"); - auto *constructor = adaptor.addConstructor(std::move(paramList)); - - constructor->addMemberInitializer("odsOperands", "values"); - constructor->addMemberInitializer("odsAttrs", "attrs"); - constructor->addMemberInitializer("odsRegions", "regions"); + auto *baseConstructor = genericAdaptorBase.addConstructor(paramList); + baseConstructor->addMemberInitializer("odsAttrs", "attrs"); + if (useProperties) + baseConstructor->addMemberInitializer("properties", "properties"); + baseConstructor->addMemberInitializer("odsRegions", "regions"); - MethodBody &body = constructor->body(); + MethodBody &body = baseConstructor->body(); body.indent() << "if (odsAttrs)\n"; body.indent() << formatv( "odsOpName.emplace(\"{0}\", odsAttrs.getContext());\n", op.getOperationName()); - } - { - auto *constructor = - adaptor.addConstructor(MethodParameter(op.getCppClassName(), "op")); - constructor->addMemberInitializer("odsOperands", "op->getOperands()"); - constructor->addMemberInitializer("odsAttrs", "op->getAttrDictionary()"); - constructor->addMemberInitializer("odsRegions", "op->getRegions()"); - constructor->addMemberInitializer("odsOpName", "op->getName()"); + paramList.insert(paramList.begin(), MethodParameter("RangeT", "values")); + auto *constructor = genericAdaptor.addConstructor(paramList); + constructor->addMemberInitializer("Base", "attrs, properties, regions"); + constructor->addMemberInitializer("odsOperands", "values"); + + // Add a forwarding constructor to the previous one that accepts + // OpaqueProperties instead and check for null and perform the cast to the + // actual properties type. + paramList[1] = MethodParameter("::mlir::DictionaryAttr", "attrs"); + paramList[2] = MethodParameter("::mlir::OpaqueProperties", "properties"); + auto *opaquePropertiesConstructor = + genericAdaptor.addConstructor(std::move(paramList)); + if (useProperties) { + opaquePropertiesConstructor->addMemberInitializer( + genericAdaptor.getClassName(), + "values, " + "attrs, " + "(properties ? *properties.as() : Properties{}), " + "regions"); + } else { + opaquePropertiesConstructor->addMemberInitializer( + genericAdaptor.getClassName(), + "values, " + "attrs, " + "(properties ? *properties.as<::mlir::EmptyProperties *>() : " + "::mlir::EmptyProperties{}), " + "regions"); + } } + // Create constructors constructing the adaptor from an instance of the op. + // This takes the attributes, properties and regions from the op instance + // and the value range from the parameter. { - auto *m = adaptor.addMethod("::mlir::ValueRange", "getOperands"); - ERROR_IF_PRUNED(m, "getOperands", op); - m->body() << " return odsOperands;"; + // Base class is in the cpp file and can simply access the members of the op + // class to initialize the template independent fields. + auto *constructor = genericAdaptorBase.addConstructor( + MethodParameter(op.getCppClassName(), "op")); + constructor->addMemberInitializer( + genericAdaptorBase.getClassName(), + llvm::Twine(!useProperties ? "op->getAttrDictionary()" + : "op->getDiscardableAttrDictionary()") + + ", op.getProperties(), op->getRegions()"); + + // Generic adaptor is templated and therefore defined inline in the header. + // We cannot use the Op class here as it is an incomplete type (we have a + // circular reference between the two). + // Use a template trick to make the constructor be instantiated at call site + // when the op class is complete. + constructor = genericAdaptor.addConstructor( + MethodParameter("RangeT", "values"), MethodParameter("LateInst", "op")); + constructor->addTemplateParam("LateInst = " + op.getCppClassName()); + constructor->addTemplateParam( + "= std::enable_if_t>"); + constructor->addMemberInitializer("Base", "op"); + constructor->addMemberInitializer("odsOperands", "values"); } + std::string sizeAttrInit; if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments")) { - sizeAttrInit = formatv(adapterSegmentSizeAttrInitCode, - emitHelper.getAttr(operandSegmentAttrName)); - } - generateNamedOperandGetters(op, adaptor, - /*isAdaptor=*/true, sizeAttrInit, - /*rangeType=*/"::mlir::ValueRange", + if (op.getDialect().usePropertiesForAttributes()) + sizeAttrInit = + formatv(adapterSegmentSizeAttrInitCodeProperties, + llvm::formatv("getProperties().operandSegmentSizes")); + else + sizeAttrInit = formatv(adapterSegmentSizeAttrInitCode, + emitHelper.getAttr(operandSegmentAttrName)); + } + generateNamedOperandGetters(op, genericAdaptor, + /*genericAdaptorBase=*/&genericAdaptorBase, + /*sizeAttrInit=*/sizeAttrInit, + /*rangeType=*/"RangeT", + /*rangeElementType=*/"ValueT", /*rangeBeginCall=*/"odsOperands.begin()", /*rangeSizeCall=*/"odsOperands.size()", /*getOperandCallPattern=*/"odsOperands[{0}]"); + // Any invalid overlap for `getOperands` will have been diagnosed before + // here already. + if (auto *m = genericAdaptor.addMethod("RangeT", "getOperands")) + m->body() << " return odsOperands;"; + FmtContext fctx; fctx.withBuilder("::mlir::Builder(odsAttrs.getContext())"); // Generate named accessor with Attribute return type. auto emitAttrWithStorageType = [&](StringRef name, StringRef emitName, Attribute attr) { - auto *method = adaptor.addMethod(attr.getStorageType(), emitName + "Attr"); + auto *method = + genericAdaptorBase.addMethod(attr.getStorageType(), emitName + "Attr"); ERROR_IF_PRUNED(method, "Adaptor::" + emitName + "Attr", op); auto &body = method->body().indent(); - body << "assert(odsAttrs && \"no attributes when constructing adapter\");\n" - << formatv("auto attr = {0}.{1}<{2}>();\n", emitHelper.getAttr(name), - attr.hasDefaultValue() || attr.isOptional() - ? "dyn_cast_or_null" - : "cast", - attr.getStorageType()); - - if (attr.hasDefaultValue()) { + if (!useProperties) + body << "assert(odsAttrs && \"no attributes when constructing " + "adapter\");\n"; + body << formatv( + "auto attr = ::llvm::{1}<{2}>({0});\n", emitHelper.getAttr(name), + attr.hasDefaultValue() || attr.isOptional() ? "dyn_cast_or_null" + : "cast", + attr.getStorageType()); + + if (attr.hasDefaultValue() && attr.isOptional()) { // Use the default value if attribute is not set. // TODO: this is inefficient, we are recreating the attribute for every // call. This should be set instead. std::string defaultValue = std::string( tgfmt(attr.getConstBuilderTemplate(), &fctx, attr.getDefaultValue())); - body << " if (!attr)\n attr = " << defaultValue << ";\n"; + body << "if (!attr)\n attr = " << defaultValue << ";\n"; } body << "return attr;\n"; }; + if (useProperties) { + auto *m = genericAdaptorBase.addInlineMethod("const Properties &", + "getProperties"); + ERROR_IF_PRUNED(m, "Adaptor::getProperties", op); + m->body() << " return properties;"; + } { - auto *m = adaptor.addMethod("::mlir::DictionaryAttr", "getAttributes"); + auto *m = + genericAdaptorBase.addMethod("::mlir::DictionaryAttr", "getAttributes"); ERROR_IF_PRUNED(m, "Adaptor::getAttributes", op); m->body() << " return odsAttrs;"; } @@ -2935,40 +3726,57 @@ OpOperandAdaptorEmitter::OpOperandAdaptorEmitter( const auto &attr = namedAttr.attr; if (attr.isDerivedAttr()) continue; - for (const auto &emitName : op.getGetterNames(name)) { - emitAttrWithStorageType(name, emitName, attr); - emitAttrGetterWithReturnType(fctx, adaptor, op, emitName, attr); - } + std::string emitName = op.getGetterName(name); + emitAttrWithStorageType(name, emitName, attr); + emitAttrGetterWithReturnType(fctx, genericAdaptorBase, op, emitName, attr); } unsigned numRegions = op.getNumRegions(); - if (numRegions > 0) { - auto *m = adaptor.addMethod("::mlir::RegionRange", "getRegions"); - ERROR_IF_PRUNED(m, "Adaptor::getRegions", op); - m->body() << " return odsRegions;"; - } for (unsigned i = 0; i < numRegions; ++i) { const auto ®ion = op.getRegion(i); if (region.name.empty()) continue; // Generate the accessors for a variadic region. - for (StringRef name : op.getGetterNames(region.name)) { - if (region.isVariadic()) { - auto *m = adaptor.addMethod("::mlir::RegionRange", name); - ERROR_IF_PRUNED(m, "Adaptor::" + name, op); - m->body() << formatv(" return odsRegions.drop_front({0});", i); - continue; - } - - auto *m = adaptor.addMethod("::mlir::Region &", name); + std::string name = op.getGetterName(region.name); + if (region.isVariadic()) { + auto *m = genericAdaptorBase.addMethod("::mlir::RegionRange", name); ERROR_IF_PRUNED(m, "Adaptor::" + name, op); - m->body() << formatv(" return *odsRegions[{0}];", i); + m->body() << formatv(" return odsRegions.drop_front({0});", i); + continue; } + + auto *m = genericAdaptorBase.addMethod("::mlir::Region &", name); + ERROR_IF_PRUNED(m, "Adaptor::" + name, op); + m->body() << formatv(" return *odsRegions[{0}];", i); + } + if (numRegions > 0) { + // Any invalid overlap for `getRegions` will have been diagnosed before + // here already. + if (auto *m = + genericAdaptorBase.addMethod("::mlir::RegionRange", "getRegions")) + m->body() << " return odsRegions;"; + } + + StringRef genericAdaptorClassName = genericAdaptor.getClassName(); + adaptor.addParent(ParentClass(genericAdaptorClassName)) + .addTemplateParam("::mlir::ValueRange"); + adaptor.declare(Visibility::Public); + adaptor.declare(genericAdaptorClassName + + "::" + genericAdaptorClassName); + { + // Constructor taking the Op as single parameter. + auto *constructor = + adaptor.addConstructor(MethodParameter(op.getCppClassName(), "op")); + constructor->addMemberInitializer(genericAdaptorClassName, + "op->getOperands(), op"); } // Add verification function. addVerification(); + + genericAdaptorBase.finalize(); + genericAdaptor.finalize(); adaptor.finalize(); } @@ -2977,10 +3785,12 @@ void OpOperandAdaptorEmitter::addVerification() { MethodParameter("::mlir::Location", "loc")); ERROR_IF_PRUNED(method, "verify", op); auto &body = method->body(); + bool useProperties = emitHelper.hasProperties(); FmtContext verifyCtx; populateSubstitutions(emitHelper, verifyCtx); - genAttributeVerifier(emitHelper, verifyCtx, body, staticVerifierEmitter); + genAttributeVerifier(emitHelper, verifyCtx, body, staticVerifierEmitter, + useProperties); body << " return ::mlir::success();"; } @@ -2989,14 +3799,26 @@ void OpOperandAdaptorEmitter::emitDecl( const Operator &op, const StaticVerifierFunctionEmitter &staticVerifierEmitter, raw_ostream &os) { - OpOperandAdaptorEmitter(op, staticVerifierEmitter).adaptor.writeDeclTo(os); + OpOperandAdaptorEmitter emitter(op, staticVerifierEmitter); + { + NamespaceEmitter ns(os, "detail"); + emitter.genericAdaptorBase.writeDeclTo(os); + } + emitter.genericAdaptor.writeDeclTo(os); + emitter.adaptor.writeDeclTo(os); } void OpOperandAdaptorEmitter::emitDef( const Operator &op, const StaticVerifierFunctionEmitter &staticVerifierEmitter, raw_ostream &os) { - OpOperandAdaptorEmitter(op, staticVerifierEmitter).adaptor.writeDefTo(os); + OpOperandAdaptorEmitter emitter(op, staticVerifierEmitter); + { + NamespaceEmitter ns(os, "detail"); + emitter.genericAdaptorBase.writeDefTo(os); + } + emitter.genericAdaptor.writeDefTo(os); + emitter.adaptor.writeDefTo(os); } // Emits the opcode enum and op classes. diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp index c3b9ded76e8b..97af7c0b29a0 100644 --- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp +++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp @@ -132,6 +132,14 @@ private: bool withKeyword; }; +/// This class represents the `prop-dict` directive. This directive represents +/// the properties of the operation, expressed as a directionary. +class PropDictDirective + : public DirectiveElementBase { +public: + explicit PropDictDirective() = default; +}; + /// This class represents the `functional-type` directive. This directive takes /// two arguments and formats them, respectively, as the inputs and results of a /// FunctionType. @@ -294,17 +302,17 @@ struct OperationFormat { }; OperationFormat(const Operator &op) - - { + : useProperties(op.getDialect().usePropertiesForAttributes() && + !op.getAttributes().empty()), + opCppClassName(op.getCppClassName()) { operandTypes.resize(op.getNumOperands(), TypeResolution()); resultTypes.resize(op.getNumResults(), TypeResolution()); hasImplicitTermTrait = llvm::any_of(op.getTraits(), [](const Trait &trait) { - return trait.getDef().isSubClassOf("SingleBlockImplicitTerminator"); + return trait.getDef().isSubClassOf("SingleBlockImplicitTerminatorImpl"); }); - hasSingleBlockTrait = - hasImplicitTermTrait || op.getTrait("::mlir::OpTrait::SingleBlock"); + hasSingleBlockTrait = op.getTrait("::mlir::OpTrait::SingleBlock"); } /// Generate the operation parser from this format. @@ -351,6 +359,12 @@ struct OperationFormat { /// A flag indicating if this operation has the SingleBlock trait. bool hasSingleBlockTrait; + /// Indicate whether attribute are stored in properties. + bool useProperties; + + /// The Operation class name + StringRef opCppClassName; + /// A map of buildable types to indices. llvm::MapVector> buildableTypes; @@ -922,7 +936,10 @@ static void genCustomParameterParser(FormatElement *param, MethodBody &body) { } /// Generate the parser for a custom directive. -static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body) { +static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body, + bool useProperties, + StringRef opCppClassName, + bool isOptional = false) { body << " {\n"; // Preprocess the directive variables. @@ -936,7 +953,7 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body) { << "OperandsLoc = parser.getCurrentLocation();\n"; if (var->isOptional()) { body << llvm::formatv( - " ::llvm::Optional<::mlir::OpAsmParser::UnresolvedOperand> " + " ::Optional<::mlir::OpAsmParser::UnresolvedOperand> " "{0}Operand;\n", var->name); } else if (var->isVariadicOfVariadic()) { @@ -965,7 +982,7 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body) { body << llvm::formatv( " {0} {1}Operand = {1}Operands.empty() ? {0}() : " "{1}Operands[0];\n", - "::llvm::Optional<::mlir::OpAsmParser::UnresolvedOperand>", + "::Optional<::mlir::OpAsmParser::UnresolvedOperand>", operand->getVar()->name); } else if (auto *type = dyn_cast(input)) { @@ -980,24 +997,35 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body) { } } - body << " if (parse" << dir->getName() << "(parser"; + body << " auto odsResult = parse" << dir->getName() << "(parser"; for (FormatElement *param : dir->getArguments()) { body << ", "; genCustomParameterParser(param, body); } + body << ");\n"; - body << "))\n" - << " return ::mlir::failure();\n"; + if (isOptional) { + body << " if (!odsResult) return {};\n" + << " if (::mlir::failed(*odsResult)) return ::mlir::failure();\n"; + } else { + body << " if (odsResult) return ::mlir::failure();\n"; + } // After parsing, add handling for any of the optional constructs. for (FormatElement *param : dir->getArguments()) { if (auto *attr = dyn_cast(param)) { const NamedAttribute *var = attr->getVar(); - if (var->attr.isOptional()) + if (var->attr.isOptional() || var->attr.hasDefaultValue()) body << llvm::formatv(" if ({0}Attr)\n ", var->name); + if (useProperties) { + body << formatv( + " result.getOrAddProperties<{1}::Properties>().{0} = {0}Attr;\n", + var->name, opCppClassName); + } else { + body << llvm::formatv(" result.addAttribute(\"{0}\", {0}Attr);\n", + var->name); + } - body << llvm::formatv(" result.addAttribute(\"{0}\", {0}Attr);\n", - var->name); } else if (auto *operand = dyn_cast(param)) { const NamedTypeConstraint *var = operand->getVar(); if (var->isOptional()) { @@ -1033,7 +1061,8 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body) { /// Generate the parser for a enum attribute. static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body, - FmtContext &attrTypeCtx) { + FmtContext &attrTypeCtx, bool parseAsOptional, + bool useProperties, StringRef opCppClassName) { Attribute baseAttr = var->attr.getBaseAttr(); const EnumAttr &enumAttr = cast(baseAttr); std::vector cases = enumAttr.getAllCases(); @@ -1043,7 +1072,7 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body, { llvm::raw_string_ostream os(attrBuilderStr); os << tgfmt(enumAttr.getConstBuilderTemplate(), &attrTypeCtx, - "attrOptional.value()"); + "*attrOptional"); } // Build a string containing the cases that can be formatted as a keyword. @@ -1057,7 +1086,7 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body, // If the attribute is not optional, build an error message for the missing // attribute. std::string errorMessage; - if (!var->attr.isOptional()) { + if (!parseAsOptional) { llvm::raw_string_ostream errorMessageOS(errorMessage); errorMessageOS << "return parser.emitError(loc, \"expected string or " @@ -1068,10 +1097,69 @@ static void genEnumAttrParser(const NamedAttribute *var, MethodBody &body, }); errorMessageOS << "]\");"; } + std::string attrAssignment; + if (useProperties) { + attrAssignment = + formatv(" " + "result.getOrAddProperties<{1}::Properties>().{0} = {0}Attr;", + var->name, opCppClassName); + } else { + attrAssignment = + formatv("result.addAttribute(\"{0}\", {0}Attr);", var->name); + } body << formatv(enumAttrParserCode, var->name, enumAttr.getCppNamespace(), enumAttr.getStringToSymbolFnName(), attrBuilderStr, - validCaseKeywordsStr, errorMessage); + validCaseKeywordsStr, errorMessage, attrAssignment); +} + +// Generate the parser for an attribute. +static void genAttrParser(AttributeVariable *attr, MethodBody &body, + FmtContext &attrTypeCtx, bool parseAsOptional, + bool useProperties, StringRef opCppClassName) { + const NamedAttribute *var = attr->getVar(); + + // Check to see if we can parse this as an enum attribute. + if (canFormatEnumAttr(var)) + return genEnumAttrParser(var, body, attrTypeCtx, parseAsOptional, + useProperties, opCppClassName); + + // Check to see if we should parse this as a symbol name attribute. + if (shouldFormatSymbolNameAttr(var)) { + body << formatv(parseAsOptional ? optionalSymbolNameAttrParserCode + : symbolNameAttrParserCode, + var->name); + } else { + + // If this attribute has a buildable type, use that when parsing the + // attribute. + std::string attrTypeStr; + if (Optional typeBuilder = attr->getTypeBuilder()) { + llvm::raw_string_ostream os(attrTypeStr); + os << tgfmt(*typeBuilder, &attrTypeCtx); + } else { + attrTypeStr = "::mlir::Type{}"; + } + if (parseAsOptional) { + body << formatv(optionalAttrParserCode, var->name, attrTypeStr); + } else { + if (attr->shouldBeQualified() || + var->attr.getStorageType() == "::mlir::Attribute") + body << formatv(genericAttrParserCode, var->name, attrTypeStr); + else + body << formatv(attrParserCode, var->name, attrTypeStr); + } + } + if (useProperties) { + body << formatv( + " if ({0}Attr) result.getOrAddProperties<{1}::Properties>().{0} = " + "{0}Attr;\n", + var->name, opCppClassName); + } else { + body << formatv( + " if ({0}Attr) result.attributes.append(\"{0}\", {0}Attr);\n", + var->name); + } } void OperationFormat::genParser(Operator &op, OpClass &opClass) { @@ -1112,17 +1200,51 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, GenContext genCtx) { /// Optional Group. if (auto *optional = dyn_cast(element)) { - ArrayRef elements = - optional->getThenElements().drop_front(optional->getParseStart()); + auto genElementParsers = [&](FormatElement *firstElement, + ArrayRef elements, + bool thenGroup) { + // If the anchor is a unit attribute, we don't need to print it. When + // parsing, we will add this attribute if this group is present. + FormatElement *elidedAnchorElement = nullptr; + auto *anchorAttr = dyn_cast(optional->getAnchor()); + if (anchorAttr && anchorAttr != firstElement && + anchorAttr->isUnitAttr()) { + elidedAnchorElement = anchorAttr; + + if (!thenGroup == optional->isInverted()) { + // Add the anchor unit attribute to the operation state. + if (useProperties) { + body << formatv( + " result.getOrAddProperties<{1}::Properties>().{0} = " + "parser.getBuilder().getUnitAttr();", + anchorAttr->getVar()->name, opCppClassName); + } else { + body << " result.addAttribute(\"" << anchorAttr->getVar()->name + << "\", parser.getBuilder().getUnitAttr());\n"; + } + } + } + + // Generate the rest of the elements inside an optional group. Elements in + // an optional group after the guard are parsed as required. + for (FormatElement *childElement : elements) + if (childElement != elidedAnchorElement) + genElementParser(childElement, body, attrTypeCtx, + GenContext::Optional); + }; + + ArrayRef thenElements = + optional->getThenElements(/*parseable=*/true); // Generate a special optional parser for the first element to gate the // parsing of the rest of the elements. - FormatElement *firstElement = elements.front(); + FormatElement *firstElement = thenElements.front(); if (auto *attrVar = dyn_cast(firstElement)) { - genElementParser(attrVar, body, attrTypeCtx); + genAttrParser(attrVar, body, attrTypeCtx, /*parseAsOptional=*/true, + useProperties, opCppClassName); body << " if (" << attrVar->getVar()->name << "Attr) {\n"; } else if (auto *literal = dyn_cast(firstElement)) { - body << " if (succeeded(parser.parseOptional"; + body << " if (::mlir::succeeded(parser.parseOptional"; genLiteralParser(literal->getSpelling(), body); body << ")) {\n"; } else if (auto *opVar = dyn_cast(firstElement)) { @@ -1142,33 +1264,28 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, body << llvm::formatv(regionEnsureSingleBlockParserCode, region->name); } - } - - // If the anchor is a unit attribute, we don't need to print it. When - // parsing, we will add this attribute if this group is present. - FormatElement *elidedAnchorElement = nullptr; - auto *anchorAttr = dyn_cast(optional->getAnchor()); - if (anchorAttr && anchorAttr != firstElement && anchorAttr->isUnitAttr()) { - elidedAnchorElement = anchorAttr; - - // Add the anchor unit attribute to the operation state. - body << " result.addAttribute(\"" << anchorAttr->getVar()->name - << "\", parser.getBuilder().getUnitAttr());\n"; - } - - // Generate the rest of the elements inside an optional group. Elements in - // an optional group after the guard are parsed as required. - for (FormatElement *childElement : llvm::drop_begin(elements, 1)) - if (childElement != elidedAnchorElement) - genElementParser(childElement, body, attrTypeCtx, GenContext::Optional); + } else if (auto *custom = dyn_cast(firstElement)) { + body << " if (auto result = [&]() -> ::mlir::OptionalParseResult {\n"; + genCustomDirectiveParser(custom, body, useProperties, opCppClassName, + /*isOptional=*/true); + body << " return ::mlir::success();\n" + << " }(); result.has_value() && ::mlir::failed(*result)) {\n" + << " return ::mlir::failure();\n" + << " } else if (result.has_value()) {\n"; + } + + genElementParsers(firstElement, thenElements.drop_front(), + /*thenGroup=*/true); body << " }"; // Generate the else elements. auto elseElements = optional->getElseElements(); if (!elseElements.empty()) { body << " else {\n"; - for (FormatElement *childElement : elseElements) - genElementParser(childElement, body, attrTypeCtx); + ArrayRef elseElements = + optional->getElseElements(/*parseable=*/true); + genElementParsers(elseElements.front(), elseElements, + /*thenGroup=*/false); body << " }"; } body << "\n"; @@ -1190,8 +1307,15 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, body << formatv(oilistParserCode, lelementName); if (AttributeVariable *unitAttrElem = oilist->getUnitAttrParsingElement(pelement)) { - body << " result.addAttribute(\"" << unitAttrElem->getVar()->name - << "\", UnitAttr::get(parser.getContext()));\n"; + if (useProperties) { + body << formatv( + " result.getOrAddProperties<{1}::Properties>().{0} = " + "parser.getBuilder().getUnitAttr();", + unitAttrElem->getVar()->name, opCppClassName); + } else { + body << " result.addAttribute(\"" << unitAttrElem->getVar()->name + << "\", UnitAttr::get(parser.getContext()));\n"; + } } else { for (FormatElement *el : pelement) genElementParser(el, body, attrTypeCtx); @@ -1215,38 +1339,10 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, /// Arguments. } else if (auto *attr = dyn_cast(element)) { - const NamedAttribute *var = attr->getVar(); - - // Check to see if we can parse this as an enum attribute. - if (canFormatEnumAttr(var)) - return genEnumAttrParser(var, body, attrTypeCtx); - - // Check to see if we should parse this as a symbol name attribute. - if (shouldFormatSymbolNameAttr(var)) { - body << formatv(var->attr.isOptional() ? optionalSymbolNameAttrParserCode - : symbolNameAttrParserCode, - var->name); - return; - } - - // If this attribute has a buildable type, use that when parsing the - // attribute. - std::string attrTypeStr; - if (Optional typeBuilder = attr->getTypeBuilder()) { - llvm::raw_string_ostream os(attrTypeStr); - os << tgfmt(*typeBuilder, &attrTypeCtx); - } else { - attrTypeStr = "::mlir::Type{}"; - } - if (genCtx == GenContext::Normal && var->attr.isOptional()) { - body << formatv(optionalAttrParserCode, var->name, attrTypeStr); - } else { - if (attr->shouldBeQualified() || - var->attr.getStorageType() == "::mlir::Attribute") - body << formatv(genericAttrParserCode, var->name, attrTypeStr); - else - body << formatv(attrParserCode, var->name, attrTypeStr); - } + bool parseAsOptional = + (genCtx == GenContext::Normal && attr->getVar()->attr.isOptional()); + genAttrParser(attr, body, attrTypeCtx, parseAsOptional, useProperties, + opCppClassName); } else if (auto *operand = dyn_cast(element)) { ArgumentLengthKind lengthKind = getArgumentLengthKind(operand->getVar()); @@ -1282,13 +1378,27 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body, /// Directives. } else if (auto *attrDict = dyn_cast(element)) { - body << " if (parser.parseOptionalAttrDict" - << (attrDict->isWithKeyword() ? "WithKeyword" : "") - << "(result.attributes))\n" + body.indent() << "{\n"; + body.indent() << "auto loc = parser.getCurrentLocation();(void)loc;\n" + << "if (parser.parseOptionalAttrDict" + << (attrDict->isWithKeyword() ? "WithKeyword" : "") + << "(result.attributes))\n" + << " return ::mlir::failure();\n"; + if (useProperties) { + body << "if (failed(verifyInherentAttrs(result.name, result.attributes, " + "[&]() {\n" + << " return parser.emitError(loc) << \"'\" << " + "result.name.getStringRef() << \"' op \";\n" + << " })))\n" + << " return ::mlir::failure();\n"; + } + body.unindent() << "}\n"; + body.unindent(); + } else if (dyn_cast(element)) { + body << " if (parseProperties(parser, result))\n" << " return ::mlir::failure();\n"; } else if (auto *customDir = dyn_cast(element)) { - genCustomDirectiveParser(customDir, body); - + genCustomDirectiveParser(customDir, body, useProperties, opCppClassName); } else if (isa(element)) { body << " ::llvm::SMLoc allOperandLoc = parser.getCurrentLocation();\n" << " if (parser.parseOperandList(allOperands))\n" @@ -2171,9 +2281,9 @@ protected: verifyCustomDirectiveArguments(SMLoc loc, ArrayRef arguments) override; /// Verify the elements of an optional group. - LogicalResult - verifyOptionalGroupElements(SMLoc loc, ArrayRef elements, - Optional anchorIndex) override; + LogicalResult verifyOptionalGroupElements(SMLoc loc, + ArrayRef elements, + FormatElement *anchor) override; LogicalResult verifyOptionalGroupElement(SMLoc loc, FormatElement *element, bool isAnchor); @@ -3104,13 +3214,10 @@ OpFormatParser::parseTypeDirectiveOperand(SMLoc loc, bool isRefChild) { return element; } -LogicalResult -OpFormatParser::verifyOptionalGroupElements(SMLoc loc, - ArrayRef elements, - Optional anchorIndex) { - for (auto &it : llvm::enumerate(elements)) { - if (failed(verifyOptionalGroupElement( - loc, it.value(), anchorIndex && *anchorIndex == it.index()))) +LogicalResult OpFormatParser::verifyOptionalGroupElements( + SMLoc loc, ArrayRef elements, FormatElement *anchor) { + for (FormatElement *element : elements) { + if (failed(verifyOptionalGroupElement(loc, element, element == anchor))) return failure(); } return success(); -- Gitee