CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
2903 lines
119 KiB
C++
2903 lines
119 KiB
C++
//===- 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/IR/Attributes.h"
|
|
#include "mlir/IR/BuiltinAttributeInterfaces.h"
|
|
#include "mlir/IR/IRMapping.h"
|
|
#include "mlir/IR/Location.h"
|
|
#include "mlir/IR/Value.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/Mangle.h"
|
|
#include "clang/Basic/Cuda.h"
|
|
#include "clang/Basic/Module.h"
|
|
#include "clang/Basic/SourceManager.h"
|
|
#include "clang/Basic/Specifiers.h"
|
|
#include "clang/Basic/TargetCXXABI.h"
|
|
#include "clang/Basic/TargetInfo.h"
|
|
#include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h"
|
|
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
|
|
#include "clang/CIR/Dialect/IR/CIRDataLayout.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/Passes.h"
|
|
#include "clang/CIR/Interfaces/ASTAttrInterfaces.h"
|
|
#include "clang/CIR/MissingFeatures.h"
|
|
#include "llvm/ADT/StringRef.h"
|
|
#include "llvm/ADT/TypeSwitch.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/VirtualFileSystem.h"
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
|
|
using namespace mlir;
|
|
using namespace cir;
|
|
|
|
namespace mlir {
|
|
#define GEN_PASS_DEF_LOWERINGPREPARE
|
|
#include "clang/CIR/Dialect/Passes.h.inc"
|
|
} // namespace mlir
|
|
|
|
static SmallString<128> getTransformedFileName(mlir::ModuleOp mlirModule) {
|
|
SmallString<128> fileName;
|
|
|
|
if (mlirModule.getSymName())
|
|
fileName = llvm::sys::path::filename(mlirModule.getSymName()->str());
|
|
|
|
if (fileName.empty())
|
|
fileName = "<null>";
|
|
|
|
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;
|
|
}
|
|
|
|
namespace {
|
|
struct LoweringPreparePass
|
|
: public impl::LoweringPrepareBase<LoweringPreparePass> {
|
|
LoweringPreparePass() = default;
|
|
|
|
// `mlir::SymbolTableCollection` is move-only (it owns lazily-created
|
|
// `unique_ptr<SymbolTable>` entries), which makes the implicit copy
|
|
// constructor ill-formed. MLIR's `clonePass()` requires copy
|
|
// construction, so define one explicitly. Per-run state members
|
|
// (dynamic initializers, guard maps, symbol-table cache, etc.) all
|
|
// start fresh in the cloned pass, which matches MLIR convention for
|
|
// pass clones and is more correct than the previous default-generated
|
|
// behavior that silently copied them.
|
|
LoweringPreparePass(const LoweringPreparePass &other)
|
|
: impl::LoweringPrepareBase<LoweringPreparePass>(other) {}
|
|
|
|
void runOnOperation() override;
|
|
|
|
void runOnOp(mlir::Operation *op);
|
|
void lowerCastOp(cir::CastOp op);
|
|
void lowerComplexDivOp(cir::ComplexDivOp op);
|
|
void lowerComplexMulOp(cir::ComplexMulOp op);
|
|
void lowerUnaryOp(cir::UnaryOpInterface op);
|
|
void lowerGetGlobalOp(cir::GetGlobalOp op);
|
|
void lowerGlobalOp(cir::GlobalOp op);
|
|
void lowerThreeWayCmpOp(cir::CmpThreeWayOp op);
|
|
void lowerArrayDtor(cir::ArrayDtor op);
|
|
void lowerArrayCtor(cir::ArrayCtor op);
|
|
void lowerTrivialCopyCall(cir::CallOp op);
|
|
void lowerStoreOfConstAggregate(cir::StoreOp op);
|
|
void lowerLocalInitOp(cir::LocalInitOp op);
|
|
|
|
/// Return the FuncOp called by `callOp`. Uses the cached `symbolTables`
|
|
/// member to avoid the O(M) module-wide scan that the static
|
|
/// `mlir::SymbolTable::lookupNearestSymbolFrom` would do per call.
|
|
cir::FuncOp getCalledFunction(cir::CallOp callOp);
|
|
|
|
/// Return a private constant cir::GlobalOp with the given type and initial
|
|
/// value, suitable for backing a memcpy-initialized local aggregate.
|
|
///
|
|
/// If a global with `baseName` (or one of its `.<n>` versioned siblings)
|
|
/// already has a matching type and initial value, that global is reused.
|
|
/// Otherwise a new global is created with the next available `.<n>` suffix
|
|
/// (matching CIRGenBuilder::createVersionedGlobal and OGCG behavior).
|
|
cir::GlobalOp getOrCreateConstAggregateGlobal(CIRBaseBuilderTy &builder,
|
|
mlir::Location loc,
|
|
llvm::StringRef baseName,
|
|
mlir::Type ty,
|
|
mlir::TypedAttr constant);
|
|
|
|
/// Build the function that initializes the specified global
|
|
cir::FuncOp buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op);
|
|
|
|
/// When looking at the 'global' op, create the wrapper function.
|
|
void defineGlobalThreadLocalWrapper(cir::GlobalOp op, cir::FuncOp initAlias,
|
|
bool isVarDefinition);
|
|
/// Create an initialization alias for a thread-local variable.
|
|
cir::FuncOp defineGlobalThreadLocalInitAlias(cir::GlobalOp op,
|
|
cir::FuncOp aliasee);
|
|
/// Get the declaration for the 'wrapper' function for a global-TLS variable.
|
|
cir::FuncOp getOrCreateThreadLocalWrapper(CIRBaseBuilderTy &builder,
|
|
cir::GlobalOp op);
|
|
// Function that generates the guard global variable, get-global, and 'if'
|
|
// condition for global TLS init function generation. This inserts an 'if'
|
|
// with the store at the beginning of the 'then' region, so inserts into the
|
|
// body should happen after that.
|
|
cir::IfOp buildGlobalTlsGuardCheck(CIRBaseBuilderTy &builder,
|
|
mlir::Location loc, cir::GlobalOp guard);
|
|
/// Handle the dtor region by registering destructor with __cxa_atexit
|
|
cir::FuncOp getOrCreateDtorFunc(CIRBaseBuilderTy &builder, cir::GlobalOp op,
|
|
mlir::Region &dtorRegion,
|
|
cir::CallOp &dtorCall);
|
|
|
|
/// Build a module init function that calls all the dynamic initializers.
|
|
void buildCXXGlobalInitFunc();
|
|
// Build an init function for all of the ordered global thread local storage
|
|
// variables.
|
|
void buildCXXGlobalTlsFunc();
|
|
|
|
/// Materialize global ctor/dtor list
|
|
void buildGlobalCtorDtorList();
|
|
|
|
cir::FuncOp buildRuntimeFunction(
|
|
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
|
|
cir::FuncType type,
|
|
cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage);
|
|
|
|
cir::GlobalOp getOrCreateRuntimeVariable(
|
|
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
|
|
mlir::Type type,
|
|
cir::GlobalLinkageKind linkage = cir::GlobalLinkageKind::ExternalLinkage,
|
|
cir::VisibilityKind visibility = cir::VisibilityKind::Default);
|
|
|
|
/// ------------
|
|
/// CUDA registration related
|
|
/// ------------
|
|
|
|
llvm::StringMap<FuncOp> cudaKernelMap;
|
|
llvm::SmallVector<std::pair<cir::GlobalOp, cir::CUDAVarRegistrationInfoAttr>>
|
|
cudaDeviceVars;
|
|
|
|
/// Build the CUDA module constructor that registers the fat binary
|
|
/// with the CUDA runtime.
|
|
void buildCUDAModuleCtor();
|
|
std::optional<FuncOp> buildCUDAModuleDtor();
|
|
std::optional<FuncOp> buildHIPModuleDtor();
|
|
std::optional<FuncOp> buildCUDARegisterGlobals();
|
|
void buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder,
|
|
FuncOp regGlobalFunc);
|
|
void buildCUDARegisterGlobalFunctions(cir::CIRBaseBuilderTy &builder,
|
|
FuncOp regGlobalFunc);
|
|
|
|
/// Handle static local variable initialization with guard variables.
|
|
void handleStaticLocal(cir::GlobalOp globalOp, cir::LocalInitOp localInitOp);
|
|
|
|
/// Get or create __cxa_guard_acquire function.
|
|
cir::FuncOp getGuardAcquireFn(cir::PointerType guardPtrTy);
|
|
|
|
/// Get or create __cxa_guard_release function.
|
|
cir::FuncOp getGuardReleaseFn(cir::PointerType guardPtrTy);
|
|
|
|
/// Get or create the __init_tls function.
|
|
cir::FuncOp getTlsInitFn();
|
|
|
|
// Create the __tls_guard variable.
|
|
cir::GlobalOp createGlobalThreadLocalGuard(CIRBaseBuilderTy &builder,
|
|
mlir::Location loc);
|
|
|
|
/// Create a guard global variable for a static local.
|
|
cir::GlobalOp createGuardGlobalOp(CIRBaseBuilderTy &builder,
|
|
mlir::Location loc, llvm::StringRef name,
|
|
cir::IntType guardTy,
|
|
cir::GlobalLinkageKind linkage);
|
|
|
|
/// Get the guard variable for a static local declaration.
|
|
cir::GlobalOp getStaticLocalDeclGuardAddress(llvm::StringRef globalSymName) {
|
|
auto it = staticLocalDeclGuardMap.find(globalSymName);
|
|
if (it != staticLocalDeclGuardMap.end())
|
|
return it->second;
|
|
return nullptr;
|
|
}
|
|
|
|
/// Set the guard variable for a static local declaration.
|
|
void setStaticLocalDeclGuardAddress(llvm::StringRef globalSymName,
|
|
cir::GlobalOp guard) {
|
|
staticLocalDeclGuardMap[globalSymName] = guard;
|
|
}
|
|
|
|
/// Get or create the guard variable for a static local declaration.
|
|
cir::GlobalOp getOrCreateStaticLocalDeclGuardAddress(
|
|
CIRBaseBuilderTy &builder, cir::GlobalOp globalOp, StringRef guardName,
|
|
bool isLocalVarDecl, bool useInt8GuardVariable) {
|
|
|
|
cir::CIRDataLayout dataLayout(mlirModule);
|
|
cir::IntType guardTy;
|
|
clang::CharUnits guardAlignment;
|
|
// Guard variables are 64 bits in the generic ABI and size width on ARM
|
|
// (i.e. 32-bit on AArch32, 64-bit on AArch64).
|
|
if (useInt8GuardVariable) {
|
|
guardTy = cir::IntType::get(&getContext(), 8, /*isSigned=*/true);
|
|
guardAlignment = clang::CharUnits::One();
|
|
} else if (useARMGuardVarABI()) {
|
|
// Guard variables are size width on ARM (32-bit AArch32, 64-bit AArch64).
|
|
const unsigned sizeTypeSize =
|
|
astCtx->getTypeSize(astCtx->getSignedSizeType());
|
|
guardTy =
|
|
cir::IntType::get(&getContext(), sizeTypeSize, /*isSigned=*/true);
|
|
guardAlignment =
|
|
clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
|
|
} else {
|
|
guardTy = cir::IntType::get(&getContext(), 64, /*isSigned=*/true);
|
|
guardAlignment =
|
|
clang::CharUnits::fromQuantity(dataLayout.getABITypeAlign(guardTy));
|
|
}
|
|
assert(guardTy && guardAlignment.getQuantity() != 0);
|
|
|
|
llvm::StringRef globalSymName = globalOp.getSymName();
|
|
cir::GlobalOp guard = getStaticLocalDeclGuardAddress(globalSymName);
|
|
if (!guard) {
|
|
// Create the guard variable with a zero-initializer.
|
|
guard = createGuardGlobalOp(builder, globalOp->getLoc(), guardName,
|
|
guardTy, globalOp.getLinkage());
|
|
guard.setInitialValueAttr(cir::IntAttr::get(guardTy, 0));
|
|
guard.setDSOLocal(globalOp.getDsoLocal());
|
|
guard.setAlignment(guardAlignment.getAsAlign().value());
|
|
guard.setTlsModel(globalOp.getTlsModel());
|
|
|
|
// The ABI says: "It is suggested that it be emitted in the same COMDAT
|
|
// group as the associated data object." In practice, this doesn't work
|
|
// for non-ELF and non-Wasm object formats, so only do it for ELF and
|
|
// Wasm.
|
|
bool hasComdat = globalOp.getComdat();
|
|
const llvm::Triple &triple = astCtx->getTargetInfo().getTriple();
|
|
// TODO(cir): for now, we're just setting comdat to true, but it should
|
|
// contain a comdat reference name here instead.
|
|
if (!isLocalVarDecl && hasComdat &&
|
|
(triple.isOSBinFormatELF() || triple.isOSBinFormatWasm())) {
|
|
// This should be a comdat for the variable.
|
|
guard.setComdat(true);
|
|
} else if (hasComdat && globalOp.isWeakForLinker()) {
|
|
guard.setComdat(true);
|
|
}
|
|
|
|
setStaticLocalDeclGuardAddress(globalSymName, guard);
|
|
}
|
|
return guard;
|
|
}
|
|
|
|
///
|
|
/// AST related
|
|
/// -----------
|
|
|
|
clang::ASTContext *astCtx;
|
|
|
|
/// Tracks current module.
|
|
mlir::ModuleOp mlirModule;
|
|
|
|
/// Cached symbol tables used to avoid repeated O(M) module-wide scans
|
|
/// during per-call/per-global symbol lookups. Lazily populated on first
|
|
/// use. Pass methods access this directly rather than threading it
|
|
/// through helper signatures (see PR feedback on #195919).
|
|
///
|
|
/// Invariant: every site that mutates the module's symbol table either
|
|
/// (a) keeps `symbolTables` in sync via
|
|
/// `symbolTables.getSymbolTable(mlirModule).insert(...)` (as
|
|
/// `getOrCreateConstAggregateGlobal` does), or (b) creates a symbol
|
|
/// that is never resolved through the cache later. Today
|
|
/// `buildRuntimeFunction` and `getOrCreateRuntimeVariable` fall in the
|
|
/// (b) bucket: their callers either use a separate map
|
|
/// (`cudaKernelMap`, `staticLocalDeclGuardMap`, `dynamicInitializers`)
|
|
/// or the static `mlir::SymbolTable::lookupNearestSymbolFrom`, never
|
|
/// the cached path. If a future change adds a cached lookup of a
|
|
/// freshly created symbol, the corresponding create site MUST move
|
|
/// to bucket (a) (insert into the cache or call
|
|
/// `invalidateSymbolTable`).
|
|
mlir::SymbolTableCollection symbolTables;
|
|
|
|
/// Tracks existing dynamic initializers.
|
|
llvm::StringMap<uint32_t> dynamicInitializerNames;
|
|
llvm::SmallVector<cir::FuncOp> dynamicInitializers;
|
|
llvm::SmallVector<cir::FuncOp> globalThreadLocalInitializers;
|
|
llvm::StringMap<cir::FuncOp> threadLocalWrappers;
|
|
llvm::StringMap<cir::FuncOp> threadLocalInitAliases;
|
|
|
|
/// Tracks guard variables for static locals (keyed by global symbol name).
|
|
llvm::StringMap<cir::GlobalOp> staticLocalDeclGuardMap;
|
|
|
|
llvm::StringMap<llvm::SmallVector<cir::GlobalOp, 1>> constAggregateGlobals;
|
|
|
|
/// List of ctors and their priorities to be called before main()
|
|
llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalCtorList;
|
|
/// List of dtors and their priorities to be called when unloading module.
|
|
llvm::SmallVector<std::pair<std::string, uint32_t>, 4> globalDtorList;
|
|
|
|
/// Returns true if the target uses ARM-style guard variables for static
|
|
/// local initialization (32-bit guard, check bit 0 only).
|
|
bool useARMGuardVarABI() const {
|
|
switch (astCtx->getCXXABIKind()) {
|
|
case clang::TargetCXXABI::GenericARM:
|
|
case clang::TargetCXXABI::iOS:
|
|
case clang::TargetCXXABI::WatchOS:
|
|
case clang::TargetCXXABI::GenericAArch64:
|
|
case clang::TargetCXXABI::WebAssembly:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void emitGlobalGuardedDtorRegion(CIRBaseBuilderTy &builder,
|
|
cir::GlobalOp global,
|
|
mlir::Region &dtorRegion, bool tls,
|
|
mlir::Block &entryBB) {
|
|
// Create a variable that binds the atexit to this shared object.
|
|
builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
|
|
cir::GlobalOp handle = getOrCreateRuntimeVariable(
|
|
builder, "__dso_handle", global.getLoc(), builder.getI8Type(),
|
|
cir::GlobalLinkageKind::ExternalLinkage, cir::VisibilityKind::Hidden);
|
|
|
|
// If this is a simple call to a destructor, get the called function.
|
|
// Otherwise, create a helper function for the entire dtor region,
|
|
// replacing the current dtor region body with a call to the helper
|
|
// function.
|
|
cir::CallOp dtorCall;
|
|
cir::FuncOp dtorFunc =
|
|
getOrCreateDtorFunc(builder, global, dtorRegion, dtorCall);
|
|
|
|
// Create a runtime helper function:
|
|
// extern "C" int __cxa_atexit(void (*f)(void *), void *p, void *d);
|
|
cir::PointerType voidPtrTy = builder.getVoidPtrTy();
|
|
cir::PointerType voidFnPtrTy = builder.getVoidFnPtrTy({voidPtrTy});
|
|
cir::PointerType handlePtrTy = builder.getPointerTo(handle.getSymType());
|
|
auto fnAtExitType =
|
|
builder.getVoidFnTy({voidFnPtrTy, voidPtrTy, handlePtrTy});
|
|
|
|
llvm::StringLiteral nameAtExit = "__cxa_atexit";
|
|
if (tls)
|
|
nameAtExit = astCtx->getTargetInfo().getTriple().isOSDarwin()
|
|
? llvm::StringLiteral("_tlv_atexit")
|
|
: llvm::StringLiteral("__cxa_thread_atexit");
|
|
|
|
cir::FuncOp fnAtExit = buildRuntimeFunction(builder, nameAtExit,
|
|
global.getLoc(), fnAtExitType);
|
|
|
|
// Replace the dtor (or helper) call with a call to
|
|
// __cxa_atexit(&dtor, &var, &__dso_handle)
|
|
builder.setInsertionPointAfter(dtorCall);
|
|
mlir::Value args[3];
|
|
auto dtorPtrTy = cir::PointerType::get(dtorFunc.getFunctionType());
|
|
args[0] = cir::GetGlobalOp::create(builder, dtorCall.getLoc(), dtorPtrTy,
|
|
dtorFunc.getSymName());
|
|
args[0] = cir::CastOp::create(builder, dtorCall.getLoc(), voidFnPtrTy,
|
|
cir::CastKind::bitcast, args[0]);
|
|
args[1] =
|
|
cir::CastOp::create(builder, dtorCall.getLoc(), voidPtrTy,
|
|
cir::CastKind::bitcast, dtorCall.getArgOperand(0));
|
|
args[2] = cir::GetGlobalOp::create(builder, handle.getLoc(), handlePtrTy,
|
|
handle.getSymName());
|
|
builder.createCallOp(dtorCall.getLoc(), fnAtExit, args);
|
|
dtorCall->erase();
|
|
mlir::Block &dtorBlock = dtorRegion.front();
|
|
entryBB.getOperations().splice(entryBB.end(), dtorBlock.getOperations(),
|
|
dtorBlock.begin(),
|
|
std::prev(dtorBlock.end()));
|
|
// make sure we leave the insert location after the operations we just
|
|
// inserted.
|
|
builder.setInsertionPointToEnd(&entryBB);
|
|
}
|
|
|
|
/// Emit the guarded initialization for a static local variable.
|
|
/// This handles the if/else structure after the guard byte check,
|
|
/// following OG's ItaniumCXXABI::EmitGuardedInit skeleton.
|
|
void emitCXXGuardedInitIf(CIRBaseBuilderTy &builder, cir::GlobalOp globalOp,
|
|
mlir::Region &ctorRegion, mlir::Region &dtorRegion,
|
|
cir::ASTVarDeclInterface varDecl,
|
|
mlir::Value guardPtr, cir::PointerType guardPtrTy,
|
|
bool threadsafe) {
|
|
auto loc = globalOp->getLoc();
|
|
|
|
// The semantics of dynamic initialization of variables with static or
|
|
// thread storage duration depends on whether they are declared at
|
|
// block-scope. The initialization of such variables at block-scope can be
|
|
// aborted with an exception and later retried (per C++20 [stmt.dcl]p4),
|
|
// and recursive entry to their initialization has undefined behavior (also
|
|
// per C++20 [stmt.dcl]p4). For such variables declared at non-block scope,
|
|
// exceptions lead to termination (per C++20 [except.terminate]p1), and
|
|
// recursive references to the variables are governed only by the lifetime
|
|
// rules (per C++20 [class.cdtor]p2), which means such references are
|
|
// perfectly fine as long as they avoid touching memory. As a result,
|
|
// block-scope variables must not be marked as initialized until after
|
|
// initialization completes (unless the mark is reverted following an
|
|
// exception), but non-block-scope variables must be marked prior to
|
|
// initialization so that recursive accesses during initialization do not
|
|
// restart initialization.
|
|
|
|
auto emitBody = [&]() {
|
|
// Emit the initializer and add a global destructor if appropriate.
|
|
mlir::Block *insertBlock = builder.getInsertionBlock();
|
|
if (!ctorRegion.empty()) {
|
|
assert(ctorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
|
|
|
|
mlir::Block &block = ctorRegion.front();
|
|
insertBlock->getOperations().splice(
|
|
insertBlock->end(), block.getOperations(), block.begin(),
|
|
std::prev(block.end()));
|
|
}
|
|
|
|
if (!dtorRegion.empty()) {
|
|
assert(dtorRegion.hasOneBlock() && "Enforced by MaxSizedRegion<1>");
|
|
|
|
emitGlobalGuardedDtorRegion(builder, globalOp, dtorRegion, !threadsafe,
|
|
*insertBlock);
|
|
}
|
|
builder.setInsertionPointToEnd(insertBlock);
|
|
ctorRegion.getBlocks().clear();
|
|
};
|
|
|
|
// Variables used when coping with thread-safe statics and exceptions.
|
|
if (threadsafe) {
|
|
// Call __cxa_guard_acquire.
|
|
cir::CallOp acquireCall = builder.createCallOp(
|
|
loc, getGuardAcquireFn(guardPtrTy), mlir::ValueRange{guardPtr});
|
|
mlir::Value acquireResult = acquireCall.getResult();
|
|
|
|
auto acquireZero = builder.getConstantInt(
|
|
loc, mlir::cast<cir::IntType>(acquireResult.getType()), 0);
|
|
auto shouldInit = builder.createCompare(loc, cir::CmpOpKind::ne,
|
|
acquireResult, acquireZero);
|
|
|
|
// Create the IfOp for the shouldInit check.
|
|
// Pass an empty callback to avoid auto-creating a yield terminator.
|
|
auto ifOp =
|
|
cir::IfOp::create(builder, loc, shouldInit, /*withElseRegion=*/false,
|
|
[](mlir::OpBuilder &, mlir::Location) {});
|
|
mlir::OpBuilder::InsertionGuard insertGuard(builder);
|
|
builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
|
|
|
|
// Call __cxa_guard_abort along the exceptional edge.
|
|
// OG: CGF.EHStack.pushCleanup<CallGuardAbort>(EHCleanup, guard);
|
|
assert(!cir::MissingFeatures::guardAbortOnException());
|
|
|
|
emitBody();
|
|
|
|
// Pop the guard-abort cleanup if we pushed one.
|
|
// OG: CGF.PopCleanupBlock();
|
|
assert(!cir::MissingFeatures::guardAbortOnException());
|
|
|
|
// Call __cxa_guard_release. This cannot throw.
|
|
builder.createCallOp(loc, getGuardReleaseFn(guardPtrTy),
|
|
mlir::ValueRange{guardPtr});
|
|
|
|
builder.createYield(loc);
|
|
} else if (!varDecl.isLocalVarDecl()) {
|
|
// For non-local variables, store 1 into the first byte of the guard
|
|
// variable before the object initialization begins so that references
|
|
// to the variable during initialization don't restart initialization.
|
|
// OG: Builder.CreateStore(llvm::ConstantInt::get(CGM.Int8Ty, 1), ...);
|
|
// Then: CGF.EmitCXXGlobalVarDeclInit(D, var, shouldPerformInit);
|
|
globalOp->emitError("NYI: non-threadsafe init for non-local variables");
|
|
return;
|
|
} else {
|
|
emitBody();
|
|
// For local variables, store 1 into the first byte of the guard variable
|
|
// after the object initialization completes so that initialization is
|
|
// retried if initialization is interrupted by an exception.
|
|
builder.createStore(
|
|
loc, builder.getConstantInt(loc, guardPtrTy.getPointee(), 1),
|
|
guardPtr);
|
|
}
|
|
|
|
builder.createYield(loc); // Outermost IfOp
|
|
}
|
|
|
|
void setASTContext(clang::ASTContext *c) { astCtx = c; }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
cir::GlobalOp LoweringPreparePass::getOrCreateRuntimeVariable(
|
|
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
|
|
mlir::Type type, cir::GlobalLinkageKind linkage,
|
|
cir::VisibilityKind visibility) {
|
|
cir::GlobalOp g = dyn_cast_or_null<cir::GlobalOp>(
|
|
mlir::SymbolTable::lookupNearestSymbolFrom(
|
|
mlirModule, mlir::StringAttr::get(mlirModule->getContext(), name)));
|
|
if (!g) {
|
|
g = cir::GlobalOp::create(builder, loc, name, type);
|
|
g.setLinkageAttr(
|
|
cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
|
|
mlir::SymbolTable::setSymbolVisibility(
|
|
g, mlir::SymbolTable::Visibility::Private);
|
|
g.setGlobalVisibility(visibility);
|
|
}
|
|
return g;
|
|
}
|
|
|
|
cir::FuncOp LoweringPreparePass::buildRuntimeFunction(
|
|
mlir::OpBuilder &builder, llvm::StringRef name, mlir::Location loc,
|
|
cir::FuncType type, cir::GlobalLinkageKind linkage) {
|
|
cir::FuncOp f = dyn_cast_or_null<FuncOp>(SymbolTable::lookupNearestSymbolFrom(
|
|
mlirModule, StringAttr::get(mlirModule->getContext(), name)));
|
|
if (!f) {
|
|
f = cir::FuncOp::create(builder, loc, name, type);
|
|
f.setLinkageAttr(
|
|
cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
|
|
mlir::SymbolTable::setSymbolVisibility(
|
|
f, mlir::SymbolTable::Visibility::Private);
|
|
|
|
assert(!cir::MissingFeatures::opFuncExtraAttrs());
|
|
}
|
|
return f;
|
|
}
|
|
|
|
static mlir::Value lowerScalarToComplexCast(mlir::MLIRContext &ctx,
|
|
cir::CastOp op) {
|
|
cir::CIRBaseBuilderTy builder(ctx);
|
|
builder.setInsertionPoint(op);
|
|
|
|
mlir::Value src = op.getSrc();
|
|
mlir::Value imag = builder.getNullValue(src.getType(), op.getLoc());
|
|
return builder.createComplexCreate(op.getLoc(), src, imag);
|
|
}
|
|
|
|
static mlir::Value lowerComplexToScalarCast(mlir::MLIRContext &ctx,
|
|
cir::CastOp op,
|
|
cir::CastKind elemToBoolKind) {
|
|
cir::CIRBaseBuilderTy builder(ctx);
|
|
builder.setInsertionPoint(op);
|
|
|
|
mlir::Value src = op.getSrc();
|
|
if (!mlir::isa<cir::BoolType>(op.getType()))
|
|
return builder.createComplexReal(op.getLoc(), src);
|
|
|
|
// Complex cast to bool: (bool)(a+bi) => (bool)a || (bool)b
|
|
mlir::Value srcReal = builder.createComplexReal(op.getLoc(), src);
|
|
mlir::Value srcImag = builder.createComplexImag(op.getLoc(), src);
|
|
|
|
cir::BoolType boolTy = builder.getBoolTy();
|
|
mlir::Value srcRealToBool =
|
|
builder.createCast(op.getLoc(), elemToBoolKind, srcReal, boolTy);
|
|
mlir::Value srcImagToBool =
|
|
builder.createCast(op.getLoc(), elemToBoolKind, srcImag, boolTy);
|
|
return builder.createLogicalOr(op.getLoc(), srcRealToBool, srcImagToBool);
|
|
}
|
|
|
|
static mlir::Value lowerComplexToComplexCast(mlir::MLIRContext &ctx,
|
|
cir::CastOp op,
|
|
cir::CastKind scalarCastKind) {
|
|
CIRBaseBuilderTy builder(ctx);
|
|
builder.setInsertionPoint(op);
|
|
|
|
mlir::Value src = op.getSrc();
|
|
auto dstComplexElemTy =
|
|
mlir::cast<cir::ComplexType>(op.getType()).getElementType();
|
|
|
|
mlir::Value srcReal = builder.createComplexReal(op.getLoc(), src);
|
|
mlir::Value srcImag = builder.createComplexImag(op.getLoc(), src);
|
|
|
|
mlir::Value dstReal = builder.createCast(op.getLoc(), scalarCastKind, srcReal,
|
|
dstComplexElemTy);
|
|
mlir::Value dstImag = builder.createCast(op.getLoc(), scalarCastKind, srcImag,
|
|
dstComplexElemTy);
|
|
return builder.createComplexCreate(op.getLoc(), dstReal, dstImag);
|
|
}
|
|
|
|
void LoweringPreparePass::lowerCastOp(cir::CastOp op) {
|
|
mlir::MLIRContext &ctx = getContext();
|
|
mlir::Value loweredValue = [&]() -> mlir::Value {
|
|
switch (op.getKind()) {
|
|
case cir::CastKind::float_to_complex:
|
|
case cir::CastKind::int_to_complex:
|
|
return lowerScalarToComplexCast(ctx, op);
|
|
case cir::CastKind::float_complex_to_real:
|
|
case cir::CastKind::int_complex_to_real:
|
|
return lowerComplexToScalarCast(ctx, op, op.getKind());
|
|
case cir::CastKind::float_complex_to_bool:
|
|
return lowerComplexToScalarCast(ctx, op, cir::CastKind::float_to_bool);
|
|
case cir::CastKind::int_complex_to_bool:
|
|
return lowerComplexToScalarCast(ctx, op, cir::CastKind::int_to_bool);
|
|
case cir::CastKind::float_complex:
|
|
return lowerComplexToComplexCast(ctx, op, cir::CastKind::floating);
|
|
case cir::CastKind::float_complex_to_int_complex:
|
|
return lowerComplexToComplexCast(ctx, op, cir::CastKind::float_to_int);
|
|
case cir::CastKind::int_complex:
|
|
return lowerComplexToComplexCast(ctx, op, cir::CastKind::integral);
|
|
case cir::CastKind::int_complex_to_float_complex:
|
|
return lowerComplexToComplexCast(ctx, op, cir::CastKind::int_to_float);
|
|
default:
|
|
return nullptr;
|
|
}
|
|
}();
|
|
|
|
if (loweredValue) {
|
|
op.replaceAllUsesWith(loweredValue);
|
|
op.erase();
|
|
}
|
|
}
|
|
|
|
static mlir::Value buildComplexBinOpLibCall(
|
|
LoweringPreparePass &pass, CIRBaseBuilderTy &builder,
|
|
llvm::StringRef (*libFuncNameGetter)(llvm::APFloat::Semantics),
|
|
mlir::Location loc, cir::ComplexType ty, mlir::Value lhsReal,
|
|
mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag) {
|
|
cir::FPTypeInterface elementTy =
|
|
mlir::cast<cir::FPTypeInterface>(ty.getElementType());
|
|
|
|
llvm::StringRef libFuncName = libFuncNameGetter(
|
|
llvm::APFloat::SemanticsToEnum(elementTy.getFloatSemantics()));
|
|
llvm::SmallVector<mlir::Type, 4> libFuncInputTypes(4, elementTy);
|
|
|
|
cir::FuncType libFuncTy = cir::FuncType::get(libFuncInputTypes, ty);
|
|
|
|
// Insert a declaration for the runtime function to be used in Complex
|
|
// multiplication and division when needed
|
|
cir::FuncOp libFunc;
|
|
{
|
|
mlir::OpBuilder::InsertionGuard ipGuard{builder};
|
|
builder.setInsertionPointToStart(pass.mlirModule.getBody());
|
|
libFunc = pass.buildRuntimeFunction(builder, libFuncName, loc, libFuncTy);
|
|
}
|
|
|
|
cir::CallOp call =
|
|
builder.createCallOp(loc, libFunc, {lhsReal, lhsImag, rhsReal, rhsImag});
|
|
return call.getResult();
|
|
}
|
|
|
|
static llvm::StringRef
|
|
getComplexDivLibCallName(llvm::APFloat::Semantics semantics) {
|
|
switch (semantics) {
|
|
case llvm::APFloat::S_IEEEhalf:
|
|
return "__divhc3";
|
|
case llvm::APFloat::S_IEEEsingle:
|
|
return "__divsc3";
|
|
case llvm::APFloat::S_IEEEdouble:
|
|
return "__divdc3";
|
|
case llvm::APFloat::S_PPCDoubleDouble:
|
|
return "__divtc3";
|
|
case llvm::APFloat::S_x87DoubleExtended:
|
|
return "__divxc3";
|
|
case llvm::APFloat::S_IEEEquad:
|
|
return "__divtc3";
|
|
default:
|
|
llvm_unreachable("unsupported floating point type");
|
|
}
|
|
}
|
|
|
|
static mlir::Value
|
|
buildAlgebraicComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc,
|
|
mlir::Value lhsReal, mlir::Value lhsImag,
|
|
mlir::Value rhsReal, mlir::Value rhsImag) {
|
|
// (a+bi) / (c+di) = ((ac+bd)/(cc+dd)) + ((bc-ad)/(cc+dd))i
|
|
mlir::Value &a = lhsReal;
|
|
mlir::Value &b = lhsImag;
|
|
mlir::Value &c = rhsReal;
|
|
mlir::Value &d = rhsImag;
|
|
|
|
mlir::Value ac = builder.createMul(loc, a, c); // a*c
|
|
mlir::Value bd = builder.createMul(loc, b, d); // b*d
|
|
mlir::Value cc = builder.createMul(loc, c, c); // c*c
|
|
mlir::Value dd = builder.createMul(loc, d, d); // d*d
|
|
mlir::Value acbd = builder.createAdd(loc, ac, bd); // ac+bd
|
|
mlir::Value ccdd = builder.createAdd(loc, cc, dd); // cc+dd
|
|
mlir::Value resultReal = builder.createDiv(loc, acbd, ccdd);
|
|
|
|
mlir::Value bc = builder.createMul(loc, b, c); // b*c
|
|
mlir::Value ad = builder.createMul(loc, a, d); // a*d
|
|
mlir::Value bcad = builder.createSub(loc, bc, ad); // bc-ad
|
|
mlir::Value resultImag = builder.createDiv(loc, bcad, ccdd);
|
|
return builder.createComplexCreate(loc, resultReal, resultImag);
|
|
}
|
|
|
|
static mlir::Value
|
|
buildRangeReductionComplexDiv(CIRBaseBuilderTy &builder, mlir::Location loc,
|
|
mlir::Value lhsReal, mlir::Value lhsImag,
|
|
mlir::Value rhsReal, mlir::Value rhsImag) {
|
|
// Implements Smith's algorithm for complex division.
|
|
// SMITH, R. L. Algorithm 116: Complex division. Commun. ACM 5, 8 (1962).
|
|
|
|
// Let:
|
|
// - lhs := a+bi
|
|
// - rhs := c+di
|
|
// - result := lhs / rhs = e+fi
|
|
//
|
|
// The algorithm pseudocode looks like follows:
|
|
// if fabs(c) >= fabs(d):
|
|
// r := d / c
|
|
// tmp := c + r*d
|
|
// e = (a + b*r) / tmp
|
|
// f = (b - a*r) / tmp
|
|
// else:
|
|
// r := c / d
|
|
// tmp := d + r*c
|
|
// e = (a*r + b) / tmp
|
|
// f = (b*r - a) / tmp
|
|
|
|
mlir::Value &a = lhsReal;
|
|
mlir::Value &b = lhsImag;
|
|
mlir::Value &c = rhsReal;
|
|
mlir::Value &d = rhsImag;
|
|
|
|
auto trueBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) {
|
|
mlir::Value r = builder.createDiv(loc, d, c); // r := d / c
|
|
mlir::Value rd = builder.createMul(loc, r, d); // r*d
|
|
mlir::Value tmp = builder.createAdd(loc, c, rd); // tmp := c + r*d
|
|
|
|
mlir::Value br = builder.createMul(loc, b, r); // b*r
|
|
mlir::Value abr = builder.createAdd(loc, a, br); // a + b*r
|
|
mlir::Value e = builder.createDiv(loc, abr, tmp);
|
|
|
|
mlir::Value ar = builder.createMul(loc, a, r); // a*r
|
|
mlir::Value bar = builder.createSub(loc, b, ar); // b - a*r
|
|
mlir::Value f = builder.createDiv(loc, bar, tmp);
|
|
|
|
mlir::Value result = builder.createComplexCreate(loc, e, f);
|
|
builder.createYield(loc, result);
|
|
};
|
|
|
|
auto falseBranchBuilder = [&](mlir::OpBuilder &, mlir::Location) {
|
|
mlir::Value r = builder.createDiv(loc, c, d); // r := c / d
|
|
mlir::Value rc = builder.createMul(loc, r, c); // r*c
|
|
mlir::Value tmp = builder.createAdd(loc, d, rc); // tmp := d + r*c
|
|
|
|
mlir::Value ar = builder.createMul(loc, a, r); // a*r
|
|
mlir::Value arb = builder.createAdd(loc, ar, b); // a*r + b
|
|
mlir::Value e = builder.createDiv(loc, arb, tmp);
|
|
|
|
mlir::Value br = builder.createMul(loc, b, r); // b*r
|
|
mlir::Value bra = builder.createSub(loc, br, a); // b*r - a
|
|
mlir::Value f = builder.createDiv(loc, bra, tmp);
|
|
|
|
mlir::Value result = builder.createComplexCreate(loc, e, f);
|
|
builder.createYield(loc, result);
|
|
};
|
|
|
|
auto cFabs = cir::FAbsOp::create(builder, loc, c);
|
|
auto dFabs = cir::FAbsOp::create(builder, loc, d);
|
|
cir::CmpOp cmpResult =
|
|
builder.createCompare(loc, cir::CmpOpKind::ge, cFabs, dFabs);
|
|
auto ternary = cir::TernaryOp::create(builder, loc, cmpResult,
|
|
trueBranchBuilder, falseBranchBuilder);
|
|
|
|
return ternary.getResult();
|
|
}
|
|
|
|
static mlir::Type higherPrecisionElementTypeForComplexArithmetic(
|
|
mlir::MLIRContext &context, clang::ASTContext &cc,
|
|
CIRBaseBuilderTy &builder, mlir::Type elementType) {
|
|
|
|
auto getHigherPrecisionFPType = [&context](mlir::Type type) -> mlir::Type {
|
|
if (mlir::isa<cir::FP16Type>(type))
|
|
return cir::SingleType::get(&context);
|
|
|
|
if (mlir::isa<cir::SingleType>(type) || mlir::isa<cir::BF16Type>(type))
|
|
return cir::DoubleType::get(&context);
|
|
|
|
if (mlir::isa<cir::DoubleType>(type))
|
|
return cir::LongDoubleType::get(&context, type);
|
|
|
|
return type;
|
|
};
|
|
|
|
auto getFloatTypeSemantics =
|
|
[&cc](mlir::Type type) -> const llvm::fltSemantics & {
|
|
const clang::TargetInfo &info = cc.getTargetInfo();
|
|
if (mlir::isa<cir::FP16Type>(type))
|
|
return info.getHalfFormat();
|
|
|
|
if (mlir::isa<cir::BF16Type>(type))
|
|
return info.getBFloat16Format();
|
|
|
|
if (mlir::isa<cir::SingleType>(type))
|
|
return info.getFloatFormat();
|
|
|
|
if (mlir::isa<cir::DoubleType>(type))
|
|
return info.getDoubleFormat();
|
|
|
|
if (mlir::isa<cir::LongDoubleType>(type)) {
|
|
if (cc.getLangOpts().OpenMP && cc.getLangOpts().OpenMPIsTargetDevice)
|
|
llvm_unreachable("NYI Float type semantics with OpenMP");
|
|
return info.getLongDoubleFormat();
|
|
}
|
|
|
|
if (mlir::isa<cir::FP128Type>(type)) {
|
|
if (cc.getLangOpts().OpenMP && cc.getLangOpts().OpenMPIsTargetDevice)
|
|
llvm_unreachable("NYI Float type semantics with OpenMP");
|
|
return info.getFloat128Format();
|
|
}
|
|
|
|
llvm_unreachable("Unsupported float type semantics");
|
|
};
|
|
|
|
const mlir::Type higherElementType = getHigherPrecisionFPType(elementType);
|
|
const llvm::fltSemantics &elementTypeSemantics =
|
|
getFloatTypeSemantics(elementType);
|
|
const llvm::fltSemantics &higherElementTypeSemantics =
|
|
getFloatTypeSemantics(higherElementType);
|
|
|
|
// Check that the promoted type can handle the intermediate values without
|
|
// overflowing. This can be interpreted as:
|
|
// (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal) * 2 <=
|
|
// LargerType.LargestFiniteVal.
|
|
// In terms of exponent it gives this formula:
|
|
// (SmallerType.LargestFiniteVal * SmallerType.LargestFiniteVal
|
|
// doubles the exponent of SmallerType.LargestFiniteVal)
|
|
if (llvm::APFloat::semanticsMaxExponent(elementTypeSemantics) * 2 + 1 <=
|
|
llvm::APFloat::semanticsMaxExponent(higherElementTypeSemantics)) {
|
|
return higherElementType;
|
|
}
|
|
|
|
// The intermediate values can't be represented in the promoted type
|
|
// without overflowing.
|
|
return {};
|
|
}
|
|
|
|
static mlir::Value
|
|
lowerComplexDiv(LoweringPreparePass &pass, CIRBaseBuilderTy &builder,
|
|
mlir::Location loc, cir::ComplexDivOp op, mlir::Value lhsReal,
|
|
mlir::Value lhsImag, mlir::Value rhsReal, mlir::Value rhsImag,
|
|
mlir::MLIRContext &mlirCx, clang::ASTContext &cc) {
|
|
cir::ComplexType complexTy = op.getType();
|
|
if (mlir::isa<cir::FPTypeInterface>(complexTy.getElementType())) {
|
|
cir::ComplexRangeKind range = op.getRange();
|
|
if (range == cir::ComplexRangeKind::Improved)
|
|
return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag,
|
|
rhsReal, rhsImag);
|
|
|
|
if (range == cir::ComplexRangeKind::Full)
|
|
return buildComplexBinOpLibCall(pass, builder, &getComplexDivLibCallName,
|
|
loc, complexTy, lhsReal, lhsImag, rhsReal,
|
|
rhsImag);
|
|
|
|
if (range == cir::ComplexRangeKind::Promoted) {
|
|
mlir::Type originalElementType = complexTy.getElementType();
|
|
mlir::Type higherPrecisionElementType =
|
|
higherPrecisionElementTypeForComplexArithmetic(mlirCx, cc, builder,
|
|
originalElementType);
|
|
|
|
if (!higherPrecisionElementType)
|
|
return buildRangeReductionComplexDiv(builder, loc, lhsReal, lhsImag,
|
|
rhsReal, rhsImag);
|
|
|
|
cir::CastKind floatingCastKind = cir::CastKind::floating;
|
|
lhsReal = builder.createCast(floatingCastKind, lhsReal,
|
|
higherPrecisionElementType);
|
|
lhsImag = builder.createCast(floatingCastKind, lhsImag,
|
|
higherPrecisionElementType);
|
|
rhsReal = builder.createCast(floatingCastKind, rhsReal,
|
|
higherPrecisionElementType);
|
|
rhsImag = builder.createCast(floatingCastKind, rhsImag,
|
|
higherPrecisionElementType);
|
|
|
|
mlir::Value algebraicResult = buildAlgebraicComplexDiv(
|
|
builder, loc, lhsReal, lhsImag, rhsReal, rhsImag);
|
|
|
|
mlir::Value resultReal = builder.createComplexReal(loc, algebraicResult);
|
|
mlir::Value resultImag = builder.createComplexImag(loc, algebraicResult);
|
|
|
|
mlir::Value finalReal =
|
|
builder.createCast(floatingCastKind, resultReal, originalElementType);
|
|
mlir::Value finalImag =
|
|
builder.createCast(floatingCastKind, resultImag, originalElementType);
|
|
return builder.createComplexCreate(loc, finalReal, finalImag);
|
|
}
|
|
}
|
|
|
|
return buildAlgebraicComplexDiv(builder, loc, lhsReal, lhsImag, rhsReal,
|
|
rhsImag);
|
|
}
|
|
|
|
void LoweringPreparePass::lowerComplexDivOp(cir::ComplexDivOp op) {
|
|
cir::CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op);
|
|
mlir::Location loc = op.getLoc();
|
|
mlir::TypedValue<cir::ComplexType> lhs = op.getLhs();
|
|
mlir::TypedValue<cir::ComplexType> rhs = op.getRhs();
|
|
mlir::Value lhsReal = builder.createComplexReal(loc, lhs);
|
|
mlir::Value lhsImag = builder.createComplexImag(loc, lhs);
|
|
mlir::Value rhsReal = builder.createComplexReal(loc, rhs);
|
|
mlir::Value rhsImag = builder.createComplexImag(loc, rhs);
|
|
|
|
mlir::Value loweredResult =
|
|
lowerComplexDiv(*this, builder, loc, op, lhsReal, lhsImag, rhsReal,
|
|
rhsImag, getContext(), *astCtx);
|
|
op.replaceAllUsesWith(loweredResult);
|
|
op.erase();
|
|
}
|
|
|
|
static llvm::StringRef
|
|
getComplexMulLibCallName(llvm::APFloat::Semantics semantics) {
|
|
switch (semantics) {
|
|
case llvm::APFloat::S_IEEEhalf:
|
|
return "__mulhc3";
|
|
case llvm::APFloat::S_IEEEsingle:
|
|
return "__mulsc3";
|
|
case llvm::APFloat::S_IEEEdouble:
|
|
return "__muldc3";
|
|
case llvm::APFloat::S_PPCDoubleDouble:
|
|
return "__multc3";
|
|
case llvm::APFloat::S_x87DoubleExtended:
|
|
return "__mulxc3";
|
|
case llvm::APFloat::S_IEEEquad:
|
|
return "__multc3";
|
|
default:
|
|
llvm_unreachable("unsupported floating point type");
|
|
}
|
|
}
|
|
|
|
static mlir::Value lowerComplexMul(LoweringPreparePass &pass,
|
|
CIRBaseBuilderTy &builder,
|
|
mlir::Location loc, cir::ComplexMulOp op,
|
|
mlir::Value lhsReal, mlir::Value lhsImag,
|
|
mlir::Value rhsReal, mlir::Value rhsImag) {
|
|
// (a+bi) * (c+di) = (ac-bd) + (ad+bc)i
|
|
mlir::Value resultRealLhs = builder.createMul(loc, lhsReal, rhsReal); // ac
|
|
mlir::Value resultRealRhs = builder.createMul(loc, lhsImag, rhsImag); // bd
|
|
mlir::Value resultImagLhs = builder.createMul(loc, lhsReal, rhsImag); // ad
|
|
mlir::Value resultImagRhs = builder.createMul(loc, lhsImag, rhsReal); // bc
|
|
mlir::Value resultReal = builder.createSub(loc, resultRealLhs, resultRealRhs);
|
|
mlir::Value resultImag = builder.createAdd(loc, resultImagLhs, resultImagRhs);
|
|
mlir::Value algebraicResult =
|
|
builder.createComplexCreate(loc, resultReal, resultImag);
|
|
|
|
cir::ComplexType complexTy = op.getType();
|
|
cir::ComplexRangeKind rangeKind = op.getRange();
|
|
if (mlir::isa<cir::IntType>(complexTy.getElementType()) ||
|
|
rangeKind == cir::ComplexRangeKind::Basic ||
|
|
rangeKind == cir::ComplexRangeKind::Improved ||
|
|
rangeKind == cir::ComplexRangeKind::Promoted)
|
|
return algebraicResult;
|
|
|
|
assert(!cir::MissingFeatures::fastMathFlags());
|
|
|
|
// Check whether the real part and the imaginary part of the result are both
|
|
// NaN. If so, emit a library call to compute the multiplication instead.
|
|
// We check a value against NaN by comparing the value against itself.
|
|
mlir::Value resultRealIsNaN = builder.createIsNaN(loc, resultReal);
|
|
mlir::Value resultImagIsNaN = builder.createIsNaN(loc, resultImag);
|
|
mlir::Value resultRealAndImagAreNaN =
|
|
builder.createLogicalAnd(loc, resultRealIsNaN, resultImagIsNaN);
|
|
|
|
return cir::TernaryOp::create(
|
|
builder, loc, resultRealAndImagAreNaN,
|
|
[&](mlir::OpBuilder &, mlir::Location) {
|
|
mlir::Value libCallResult = buildComplexBinOpLibCall(
|
|
pass, builder, &getComplexMulLibCallName, loc, complexTy,
|
|
lhsReal, lhsImag, rhsReal, rhsImag);
|
|
builder.createYield(loc, libCallResult);
|
|
},
|
|
[&](mlir::OpBuilder &, mlir::Location) {
|
|
builder.createYield(loc, algebraicResult);
|
|
})
|
|
.getResult();
|
|
}
|
|
|
|
void LoweringPreparePass::lowerComplexMulOp(cir::ComplexMulOp op) {
|
|
cir::CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op);
|
|
mlir::Location loc = op.getLoc();
|
|
mlir::TypedValue<cir::ComplexType> lhs = op.getLhs();
|
|
mlir::TypedValue<cir::ComplexType> rhs = op.getRhs();
|
|
mlir::Value lhsReal = builder.createComplexReal(loc, lhs);
|
|
mlir::Value lhsImag = builder.createComplexImag(loc, lhs);
|
|
mlir::Value rhsReal = builder.createComplexReal(loc, rhs);
|
|
mlir::Value rhsImag = builder.createComplexImag(loc, rhs);
|
|
mlir::Value loweredResult = lowerComplexMul(*this, builder, loc, op, lhsReal,
|
|
lhsImag, rhsReal, rhsImag);
|
|
op.replaceAllUsesWith(loweredResult);
|
|
op.erase();
|
|
}
|
|
|
|
void LoweringPreparePass::lowerUnaryOp(cir::UnaryOpInterface op) {
|
|
if (!mlir::isa<cir::ComplexType>(op.getResult().getType()))
|
|
return;
|
|
|
|
mlir::Location loc = op->getLoc();
|
|
CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op);
|
|
|
|
mlir::Value operand = op.getInput();
|
|
mlir::Value operandReal = builder.createComplexReal(loc, operand);
|
|
mlir::Value operandImag = builder.createComplexImag(loc, operand);
|
|
|
|
mlir::Value resultReal = operandReal;
|
|
mlir::Value resultImag = operandImag;
|
|
|
|
llvm::TypeSwitch<mlir::Operation *>(op)
|
|
.Case<cir::IncOp>(
|
|
[&](auto) { resultReal = builder.createInc(loc, operandReal); })
|
|
.Case<cir::DecOp>(
|
|
[&](auto) { resultReal = builder.createDec(loc, operandReal); })
|
|
.Case<cir::MinusOp>([&](auto) {
|
|
resultReal = builder.createMinus(loc, operandReal);
|
|
resultImag = builder.createMinus(loc, operandImag);
|
|
})
|
|
.Case<cir::NotOp>(
|
|
[&](auto) { resultImag = builder.createMinus(loc, operandImag); })
|
|
.Default([](auto) { llvm_unreachable("unhandled unary complex op"); });
|
|
|
|
mlir::Value result = builder.createComplexCreate(loc, resultReal, resultImag);
|
|
op->replaceAllUsesWith(mlir::ValueRange{result});
|
|
op->erase();
|
|
}
|
|
|
|
cir::FuncOp LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder,
|
|
cir::GlobalOp op,
|
|
mlir::Region &dtorRegion,
|
|
cir::CallOp &dtorCall) {
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
assert(!cir::MissingFeatures::astVarDeclInterface());
|
|
|
|
cir::VoidType voidTy = builder.getVoidTy();
|
|
auto voidPtrTy = cir::PointerType::get(voidTy);
|
|
|
|
// Look for operations in dtorBlock
|
|
mlir::Block &dtorBlock = dtorRegion.front();
|
|
|
|
// The first operation should be a get_global to retrieve the address
|
|
// of the global variable we're destroying.
|
|
auto opIt = dtorBlock.getOperations().begin();
|
|
cir::GetGlobalOp ggop = mlir::cast<cir::GetGlobalOp>(*opIt);
|
|
|
|
// The simple case is just a call to a destructor, like this:
|
|
//
|
|
// %0 = cir.get_global %globalS : !cir.ptr<!rec_S>
|
|
// cir.call %_ZN1SD1Ev(%0) : (!cir.ptr<!rec_S>) -> ()
|
|
// (implicit cir.yield)
|
|
//
|
|
// That is, if the second operation is a call that takes the get_global result
|
|
// as its only operand, and the only other operation is a yield, then we can
|
|
// just return the called function.
|
|
if (dtorBlock.getOperations().size() == 3) {
|
|
auto callOp = mlir::dyn_cast<cir::CallOp>(&*(++opIt));
|
|
auto yieldOp = mlir::dyn_cast<cir::YieldOp>(&*(++opIt));
|
|
if (yieldOp && callOp && callOp.getNumOperands() == 1 &&
|
|
callOp.getArgOperand(0) == ggop) {
|
|
dtorCall = callOp;
|
|
return getCalledFunction(callOp);
|
|
}
|
|
}
|
|
|
|
// Otherwise, we need to create a helper function to replace the dtor region.
|
|
// This name is kind of arbitrary, but it matches the name that classic
|
|
// codegen uses, based on the expected case that gets us here.
|
|
builder.setInsertionPointAfter(op);
|
|
SmallString<256> fnName("__cxx_global_array_dtor");
|
|
uint32_t cnt = dynamicInitializerNames[fnName]++;
|
|
if (cnt)
|
|
fnName += "." + std::to_string(cnt);
|
|
|
|
// Create the helper function.
|
|
auto fnType = cir::FuncType::get({voidPtrTy}, voidTy);
|
|
cir::FuncOp dtorFunc =
|
|
buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
|
|
cir::GlobalLinkageKind::InternalLinkage);
|
|
|
|
SmallVector<mlir::NamedAttribute> paramAttrs;
|
|
paramAttrs.push_back(
|
|
builder.getNamedAttr("llvm.noundef", builder.getUnitAttr()));
|
|
SmallVector<mlir::Attribute> argAttrDicts;
|
|
argAttrDicts.push_back(
|
|
mlir::DictionaryAttr::get(builder.getContext(), paramAttrs));
|
|
dtorFunc.setArgAttrsAttr(
|
|
mlir::ArrayAttr::get(builder.getContext(), argAttrDicts));
|
|
|
|
mlir::Block *entryBB = dtorFunc.addEntryBlock();
|
|
|
|
// Move everything from the dtor region into the helper function.
|
|
entryBB->getOperations().splice(entryBB->begin(), dtorBlock.getOperations(),
|
|
dtorBlock.begin(), dtorBlock.end());
|
|
|
|
// Before erasing this, clone it back into the dtor region
|
|
cir::GetGlobalOp dtorGGop =
|
|
mlir::cast<cir::GetGlobalOp>(entryBB->getOperations().front());
|
|
builder.setInsertionPointToStart(&dtorBlock);
|
|
builder.clone(*dtorGGop.getOperation());
|
|
|
|
// Replace all uses of the help function's get_global with the function
|
|
// argument.
|
|
mlir::Value dtorArg = entryBB->getArgument(0);
|
|
dtorGGop.replaceAllUsesWith(dtorArg);
|
|
dtorGGop.erase();
|
|
|
|
// Replace the yield in the final block with a return
|
|
mlir::Block &finalBlock = dtorFunc.getBody().back();
|
|
auto yieldOp = cast<cir::YieldOp>(finalBlock.getTerminator());
|
|
builder.setInsertionPoint(yieldOp);
|
|
cir::ReturnOp::create(builder, yieldOp->getLoc());
|
|
yieldOp->erase();
|
|
|
|
// Create a call to the helper function, passing the original get_global op
|
|
// as the argument.
|
|
cir::GetGlobalOp origGGop =
|
|
mlir::cast<cir::GetGlobalOp>(dtorBlock.getOperations().front());
|
|
builder.setInsertionPointAfter(origGGop);
|
|
mlir::Value ggopResult = origGGop.getResult();
|
|
dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult);
|
|
|
|
// Add a yield after the call.
|
|
auto finalYield = cir::YieldOp::create(builder, op.getLoc());
|
|
|
|
// Erase everything after the yield.
|
|
dtorBlock.getOperations().erase(std::next(mlir::Block::iterator(finalYield)),
|
|
dtorBlock.end());
|
|
dtorRegion.getBlocks().erase(std::next(dtorRegion.begin()), dtorRegion.end());
|
|
|
|
return dtorFunc;
|
|
}
|
|
|
|
cir::FuncOp
|
|
LoweringPreparePass::buildCXXGlobalVarDeclInitFunc(cir::GlobalOp op) {
|
|
// TODO(cir): Store this in the GlobalOp.
|
|
// This should come from the MangleContext, but for now I'm hardcoding it.
|
|
SmallString<256> fnName("__cxx_global_var_init");
|
|
// Get a unique name
|
|
uint32_t cnt = dynamicInitializerNames[fnName]++;
|
|
if (cnt)
|
|
fnName += "." + std::to_string(cnt);
|
|
|
|
// Create a variable initialization function.
|
|
CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op);
|
|
cir::VoidType voidTy = builder.getVoidTy();
|
|
auto fnType = cir::FuncType::get({}, voidTy);
|
|
FuncOp f = buildRuntimeFunction(builder, fnName, op.getLoc(), fnType,
|
|
cir::GlobalLinkageKind::InternalLinkage);
|
|
|
|
// Move over the initialization code of the ctor region.
|
|
// The ctor region may have multiple blocks when exception handling
|
|
// scaffolding creates extra blocks (e.g., unreachable/trap blocks).
|
|
// We move all operations from the first block (minus the yield) into
|
|
// the function entry, and discard extra blocks (which contain only
|
|
// unreachable terminators from EH cleanup paths).
|
|
mlir::Block *entryBB = f.addEntryBlock();
|
|
builder.setInsertionPointToStart(entryBB);
|
|
|
|
// If this is a global TLS variable (that is, declared at namespace scope), we
|
|
// have to emit the guard variable here.
|
|
bool needsTlsGuard = op.getDynTlsRefs() && op.getDynTlsRefs()->getGuardName();
|
|
cir::IfOp guardIf;
|
|
if (needsTlsGuard) {
|
|
guardIf = buildGlobalTlsGuardCheck(
|
|
builder, op.getLoc(),
|
|
getOrCreateStaticLocalDeclGuardAddress(
|
|
builder, op, op.getDynTlsRefs()->getGuardName().getValue(),
|
|
/*isLocalVarDecl=*/false,
|
|
/*useInt8GuardVariable=*/op.hasInternalLinkage()));
|
|
builder.setInsertionPointToEnd(&guardIf.getThenRegion().front());
|
|
}
|
|
|
|
if (!op.getCtorRegion().empty()) {
|
|
mlir::Block &block = op.getCtorRegion().front();
|
|
mlir::Block *insertBlock = builder.getBlock();
|
|
insertBlock->getOperations().splice(insertBlock->end(),
|
|
block.getOperations(), block.begin(),
|
|
std::prev(block.end()));
|
|
}
|
|
|
|
// Register the destructor call with __cxa_atexit
|
|
mlir::Region &dtorRegion = op.getDtorRegion();
|
|
if (!dtorRegion.empty()) {
|
|
assert(!cir::MissingFeatures::astVarDeclInterface());
|
|
|
|
emitGlobalGuardedDtorRegion(builder, op, dtorRegion,
|
|
op.getTlsModel().has_value(),
|
|
*builder.getBlock());
|
|
}
|
|
|
|
// If we're actually in the 'if' above, create a yield.
|
|
if (needsTlsGuard) {
|
|
builder.setInsertionPointToEnd(&guardIf.getThenRegion().back());
|
|
cir::YieldOp::create(builder, op.getLoc());
|
|
}
|
|
|
|
// Replace cir.yield with cir.return
|
|
builder.setInsertionPointToEnd(entryBB);
|
|
mlir::Operation *yieldOp = nullptr;
|
|
if (!op.getCtorRegion().empty()) {
|
|
mlir::Block &block = op.getCtorRegion().front();
|
|
yieldOp = &block.getOperations().back();
|
|
} else {
|
|
assert(!dtorRegion.empty());
|
|
mlir::Block &block = dtorRegion.front();
|
|
yieldOp = &block.getOperations().back();
|
|
}
|
|
|
|
assert(isa<cir::YieldOp>(*yieldOp));
|
|
cir::ReturnOp::create(builder, yieldOp->getLoc());
|
|
return f;
|
|
}
|
|
|
|
cir::FuncOp
|
|
LoweringPreparePass::getGuardAcquireFn(cir::PointerType guardPtrTy) {
|
|
// int __cxa_guard_acquire(__guard *guard_object);
|
|
CIRBaseBuilderTy builder(getContext());
|
|
mlir::OpBuilder::InsertionGuard ipGuard{builder};
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
cir::IntType intTy = cir::IntType::get(&getContext(), 32, /*isSigned=*/true);
|
|
auto fnType = cir::FuncType::get({guardPtrTy}, intTy);
|
|
return buildRuntimeFunction(builder, "__cxa_guard_acquire", loc, fnType);
|
|
}
|
|
|
|
cir::FuncOp
|
|
LoweringPreparePass::getGuardReleaseFn(cir::PointerType guardPtrTy) {
|
|
// void __cxa_guard_release(__guard *guard_object);
|
|
CIRBaseBuilderTy builder(getContext());
|
|
mlir::OpBuilder::InsertionGuard ipGuard{builder};
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
cir::VoidType voidTy = cir::VoidType::get(&getContext());
|
|
auto fnType = cir::FuncType::get({guardPtrTy}, voidTy);
|
|
return buildRuntimeFunction(builder, "__cxa_guard_release", loc, fnType);
|
|
}
|
|
|
|
cir::FuncOp LoweringPreparePass::getTlsInitFn() {
|
|
// void __tls_init(void);
|
|
CIRBaseBuilderTy builder(getContext());
|
|
mlir::OpBuilder::InsertionGuard _{builder};
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
auto fnType = builder.getVoidFnTy();
|
|
return buildRuntimeFunction(builder, "__tls_init", loc, fnType,
|
|
cir::GlobalLinkageKind::InternalLinkage);
|
|
}
|
|
|
|
cir::GlobalOp LoweringPreparePass::createGuardGlobalOp(
|
|
CIRBaseBuilderTy &builder, mlir::Location loc, llvm::StringRef name,
|
|
cir::IntType guardTy, cir::GlobalLinkageKind linkage) {
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
cir::GlobalOp g = cir::GlobalOp::create(builder, loc, name, guardTy);
|
|
g.setLinkageAttr(
|
|
cir::GlobalLinkageKindAttr::get(builder.getContext(), linkage));
|
|
mlir::SymbolTable::setSymbolVisibility(
|
|
g, mlir::SymbolTable::Visibility::Private);
|
|
return g;
|
|
}
|
|
|
|
void LoweringPreparePass::handleStaticLocal(cir::GlobalOp globalOp,
|
|
cir::LocalInitOp localInitOp) {
|
|
CIRBaseBuilderTy builder(getContext());
|
|
|
|
std::optional<cir::ASTVarDeclInterface> astOption = globalOp.getAst();
|
|
assert(astOption.has_value());
|
|
cir::ASTVarDeclInterface varDecl = astOption.value();
|
|
|
|
builder.setInsertionPointAfter(localInitOp);
|
|
mlir::Block *localInitBlock = builder.getInsertionBlock();
|
|
|
|
// Remove the terminator temporarily - we'll add it back at the end.
|
|
mlir::Operation *ret = localInitBlock->getTerminator();
|
|
ret->remove();
|
|
// Note: These two insert-point-after sets are necessary, as the 'trailing'
|
|
// operation has changed thanks to the terminator removal.
|
|
builder.setInsertionPointAfter(localInitOp);
|
|
|
|
// Inline variables that weren't instantiated from variable templates have
|
|
// partially-ordered initialization within their translation unit.
|
|
bool nonTemplateInline =
|
|
varDecl.isInline() &&
|
|
!clang::isTemplateInstantiation(varDecl.getTemplateSpecializationKind());
|
|
|
|
// Inline namespace-scope variables require guarded initialization in a
|
|
// __cxx_global_var_init function. This is not yet implemented.
|
|
if (nonTemplateInline) {
|
|
globalOp->emitError(
|
|
"NYI: guarded initialization for inline namespace-scope variables");
|
|
return;
|
|
}
|
|
|
|
// We only need to use thread-safe statics for local non-TLS variables and
|
|
// inline variables; other global initialization is always single-threaded
|
|
// or (through lazy dynamic loading in multiple threads) unsequenced.
|
|
bool threadsafe = astCtx->getLangOpts().ThreadsafeStatics &&
|
|
(varDecl.isLocalVarDecl() || nonTemplateInline) &&
|
|
!varDecl.getTLSKind();
|
|
|
|
// If we have a global variable with internal linkage and thread-safe statics
|
|
// are disabled, we can just let the guard variable be of type i8.
|
|
bool useInt8GuardVariable = !threadsafe && globalOp.hasInternalLinkage();
|
|
|
|
// Create the guard variable if we don't already have it.
|
|
cir::GlobalOp guard = getOrCreateStaticLocalDeclGuardAddress(
|
|
builder, globalOp, globalOp.getStaticLocalGuard()->getName().getValue(),
|
|
varDecl.isLocalVarDecl(), useInt8GuardVariable);
|
|
if (!guard) {
|
|
// Error was already emitted, just restore the terminator and return.
|
|
localInitBlock->push_back(ret);
|
|
return;
|
|
}
|
|
|
|
mlir::Value guardPtr = builder.createGetGlobal(guard, localInitOp.getTls());
|
|
|
|
// Test whether the variable has completed initialization.
|
|
//
|
|
// Itanium C++ ABI 3.3.2:
|
|
// The following is pseudo-code showing how these functions can be used:
|
|
// if (obj_guard.first_byte == 0) {
|
|
// if ( __cxa_guard_acquire (&obj_guard) ) {
|
|
// try {
|
|
// ... initialize the object ...;
|
|
// } catch (...) {
|
|
// __cxa_guard_abort (&obj_guard);
|
|
// throw;
|
|
// }
|
|
// ... queue object destructor with __cxa_atexit() ...;
|
|
// __cxa_guard_release (&obj_guard);
|
|
// }
|
|
// }
|
|
//
|
|
// If threadsafe statics are enabled, but we don't have inline atomics, just
|
|
// call __cxa_guard_acquire unconditionally. The "inline" check isn't
|
|
// actually inline, and the user might not expect calls to __atomic libcalls.
|
|
unsigned maxInlineWidthInBits =
|
|
astCtx->getTargetInfo().getMaxAtomicInlineWidth();
|
|
|
|
if (!threadsafe || maxInlineWidthInBits) {
|
|
// Load the first byte of the guard variable.
|
|
auto bytePtrTy = cir::PointerType::get(builder.getSIntNTy(8));
|
|
mlir::Value bytePtr = builder.createBitcast(guardPtr, bytePtrTy);
|
|
mlir::Value guardLoad = builder.createAlignedLoad(
|
|
localInitOp.getLoc(), bytePtr, *guard.getAlignment());
|
|
|
|
// Itanium ABI:
|
|
// An implementation supporting thread-safety on multiprocessor
|
|
// systems must also guarantee that references to the initialized
|
|
// object do not occur before the load of the initialization flag.
|
|
//
|
|
// In LLVM, we do this by marking the load Acquire.
|
|
if (threadsafe) {
|
|
auto loadOp = mlir::cast<cir::LoadOp>(guardLoad.getDefiningOp());
|
|
loadOp.setMemOrder(cir::MemOrder::Acquire);
|
|
loadOp.setSyncScope(cir::SyncScopeKind::System);
|
|
}
|
|
|
|
// For ARM, we should only check the first bit, rather than the entire byte:
|
|
//
|
|
// ARM C++ ABI 3.2.3.1:
|
|
// To support the potential use of initialization guard variables
|
|
// as semaphores that are the target of ARM SWP and LDREX/STREX
|
|
// synchronizing instructions we define a static initialization
|
|
// guard variable to be a 4-byte aligned, 4-byte word with the
|
|
// following inline access protocol.
|
|
// #define INITIALIZED 1
|
|
// if ((obj_guard & INITIALIZED) != INITIALIZED) {
|
|
// if (__cxa_guard_acquire(&obj_guard))
|
|
// ...
|
|
// }
|
|
//
|
|
// and similarly for ARM64:
|
|
//
|
|
// ARM64 C++ ABI 3.2.2:
|
|
// This ABI instead only specifies the value bit 0 of the static guard
|
|
// variable; all other bits are platform defined. Bit 0 shall be 0 when
|
|
// the variable is not initialized and 1 when it is.
|
|
if (useARMGuardVarABI() && !useInt8GuardVariable) {
|
|
auto one = builder.getConstantInt(
|
|
localInitOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()),
|
|
1);
|
|
guardLoad = builder.createAnd(localInitOp.getLoc(), guardLoad, one);
|
|
}
|
|
|
|
// Check if the first byte of the guard variable is zero.
|
|
auto zero = builder.getConstantInt(
|
|
localInitOp.getLoc(), mlir::cast<cir::IntType>(guardLoad.getType()), 0);
|
|
auto needsInit = builder.createCompare(localInitOp.getLoc(),
|
|
cir::CmpOpKind::eq, guardLoad, zero);
|
|
|
|
// Build the guarded initialization inside an if block.
|
|
cir::IfOp::create(
|
|
builder, globalOp.getLoc(), needsInit,
|
|
/*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location) {
|
|
emitCXXGuardedInitIf(builder, globalOp, localInitOp.getCtorRegion(),
|
|
localInitOp.getDtorRegion(), varDecl, guardPtr,
|
|
builder.getPointerTo(guard.getSymType()),
|
|
threadsafe);
|
|
});
|
|
} else {
|
|
// Threadsafe statics without inline atomics - call __cxa_guard_acquire
|
|
// unconditionally without the initial guard byte check.
|
|
globalOp->emitError("NYI: guarded init without inline atomics support");
|
|
return;
|
|
}
|
|
|
|
// Insert the removed terminator back.
|
|
builder.getInsertionBlock()->push_back(ret);
|
|
}
|
|
|
|
void LoweringPreparePass::lowerLocalInitOp(cir::LocalInitOp initOp) {
|
|
|
|
// If we don't actually need to initialize anything anymore, we're done here.
|
|
if (initOp.getCtorRegion().empty() && initOp.getDtorRegion().empty()) {
|
|
initOp.erase();
|
|
return;
|
|
}
|
|
|
|
cir::GlobalOp globalOp = initOp.getReferencedGlobal(symbolTables);
|
|
assert(globalOp && "No global-op found");
|
|
|
|
handleStaticLocal(globalOp, initOp);
|
|
|
|
// Remove the init local op, now that we've done everything we need with it.
|
|
initOp.erase();
|
|
}
|
|
static bool isThreadWrapperReplaceable(cir::TLS_Model tls,
|
|
clang::ASTContext &astCtx) {
|
|
return tls == cir::TLS_Model::GeneralDynamic &&
|
|
astCtx.getTargetInfo().getTriple().isOSDarwin();
|
|
}
|
|
|
|
static cir::GlobalLinkageKind
|
|
getThreadLocalWrapperLinkage(GlobalOp op, clang::ASTContext &astCtx) {
|
|
if (isLocalLinkage(op.getLinkage()))
|
|
return op.getLinkage();
|
|
|
|
if (isThreadWrapperReplaceable(*op.getTlsModel(), astCtx))
|
|
if (!isLinkOnceLinkage(op.getLinkage()) &&
|
|
!isWeakODRLinkage(op.getLinkage()))
|
|
return op.getLinkage();
|
|
|
|
// If this isn't a TU in which this variable is defined, the thread wrapper is
|
|
// discardable.
|
|
if (op.isDeclaration())
|
|
return cir::GlobalLinkageKind::LinkOnceODRLinkage;
|
|
return cir::GlobalLinkageKind::WeakODRLinkage;
|
|
}
|
|
|
|
cir::FuncOp
|
|
LoweringPreparePass::getOrCreateThreadLocalWrapper(CIRBaseBuilderTy &builder,
|
|
GlobalOp op) {
|
|
mlir::OpBuilder::InsertionGuard insertGuard(builder);
|
|
builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
|
|
|
|
mlir::StringAttr wrapperName = op.getDynTlsRefs()->getWrapperName();
|
|
|
|
auto existingWrapperIter = threadLocalWrappers.find(wrapperName.getValue());
|
|
if (existingWrapperIter != threadLocalWrappers.end())
|
|
return existingWrapperIter->second;
|
|
|
|
// type is ptr-to-global-type(void);
|
|
auto funcType = cir::FuncType::get({}, builder.getPointerTo(op.getSymType()));
|
|
cir::FuncOp func =
|
|
cir::FuncOp::create(builder, op.getLoc(), wrapperName, funcType);
|
|
|
|
cir::GlobalLinkageKind linkageKind =
|
|
getThreadLocalWrapperLinkage(op, *astCtx);
|
|
func.setLinkageAttr(
|
|
cir::GlobalLinkageKindAttr::get(&getContext(), linkageKind));
|
|
|
|
// TODO(cir): This is supposed to refer to the comdat of the global symbol,
|
|
// but that isn't in CIR yet.
|
|
if (astCtx->getTargetInfo().getTriple().supportsCOMDAT() &&
|
|
func.isWeakForLinker())
|
|
func.setComdat(true);
|
|
|
|
mlir::SymbolTable::setSymbolVisibility(
|
|
func, mlir::SymbolTable::Visibility::Private);
|
|
|
|
if (!isLocalLinkage(linkageKind)) {
|
|
if (!isThreadWrapperReplaceable(*op.getTlsModel(), *astCtx) ||
|
|
isLinkOnceLinkage(linkageKind) || isWeakODRLinkage(linkageKind) ||
|
|
op.getGlobalVisibility() == cir::VisibilityKind::Hidden)
|
|
func.setGlobalVisibility(cir::VisibilityKind::Hidden);
|
|
}
|
|
if (isThreadWrapperReplaceable(*op.getTlsModel(), *astCtx))
|
|
op->emitError("Unhandled thread wrapper attributes for CC and Nounwind");
|
|
|
|
threadLocalWrappers.insert({wrapperName.getValue(), func});
|
|
return func;
|
|
}
|
|
|
|
void LoweringPreparePass::defineGlobalThreadLocalWrapper(cir::GlobalOp op,
|
|
cir::FuncOp initAlias,
|
|
bool isVarDefinition) {
|
|
CIRBaseBuilderTy builder(getContext());
|
|
cir::FuncOp wrapper = getOrCreateThreadLocalWrapper(builder, op);
|
|
mlir::Block *entryBB = wrapper.addEntryBlock();
|
|
builder.setInsertionPointToStart(entryBB);
|
|
// If we are a situation where we have/need one, emit a call to the init
|
|
// function.
|
|
if (initAlias) {
|
|
mlir::Location aliasLoc = initAlias.getLoc();
|
|
if (!isVarDefinition) {
|
|
// If this isn't a definition, we have to check that the alias exists.
|
|
mlir::Value funcLoad = cir::GetGlobalOp::create(
|
|
builder, aliasLoc, cir::PointerType::get(initAlias.getFunctionType()),
|
|
initAlias.getSymName());
|
|
mlir::Value nullCheck =
|
|
builder.getNullValue(funcLoad.getType(), aliasLoc);
|
|
mlir::Value cmp = cir::CmpOp::create(
|
|
builder, aliasLoc, cir::CmpOpKind::ne, funcLoad, nullCheck);
|
|
cir::IfOp::create(builder, aliasLoc, cmp, /*withElseRegion=*/false,
|
|
[&](mlir::OpBuilder &, mlir::Location loc) {
|
|
builder.createCallOp(aliasLoc, initAlias, {});
|
|
cir::YieldOp::create(builder, aliasLoc);
|
|
});
|
|
} else {
|
|
// If this IS a definition, we know the alias exists, so we can just emit
|
|
// a call to it.
|
|
builder.createCallOp(aliasLoc, initAlias, {});
|
|
}
|
|
}
|
|
cir::GetGlobalOp get = builder.createGetGlobal(op, /*tls=*/true);
|
|
cir::ReturnOp::create(builder, op.getLoc(), {get});
|
|
}
|
|
|
|
cir::FuncOp
|
|
LoweringPreparePass::defineGlobalThreadLocalInitAlias(cir::GlobalOp op,
|
|
cir::FuncOp aliasee) {
|
|
CIRBaseBuilderTy builder(getContext());
|
|
mlir::OpBuilder::InsertionGuard insertGuard(builder);
|
|
builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
|
|
mlir::StringAttr aliasName = op.getDynTlsRefs()->getInitName();
|
|
auto existingAliasIter = threadLocalInitAliases.find(aliasName.getValue());
|
|
|
|
if (existingAliasIter != threadLocalInitAliases.end())
|
|
return existingAliasIter->second;
|
|
|
|
auto funcType = builder.getVoidFnTy();
|
|
cir::FuncOp alias =
|
|
cir::FuncOp::create(builder, op.getLoc(), aliasName, funcType);
|
|
alias.setLinkage(op.getLinkage());
|
|
|
|
if (aliasee) {
|
|
alias.setAliasee(aliasee.getSymName());
|
|
} else {
|
|
// If we don't have anything to alias (because this isn't a variable
|
|
// definition!), we set this as just a function definition with no alias,
|
|
// and extern-weak.
|
|
alias.setLinkage(cir::GlobalLinkageKind::ExternalWeakLinkage);
|
|
mlir::SymbolTable::setSymbolVisibility(
|
|
alias, mlir::SymbolTable::Visibility::Private);
|
|
}
|
|
|
|
threadLocalInitAliases.insert({aliasName.getValue(), alias});
|
|
return alias;
|
|
}
|
|
|
|
void LoweringPreparePass::lowerGlobalOp(GlobalOp op) {
|
|
// Static locals are handled separately via guard variables.
|
|
if (op.getStaticLocalGuard())
|
|
return;
|
|
|
|
mlir::Region &ctorRegion = op.getCtorRegion();
|
|
mlir::Region &dtorRegion = op.getDtorRegion();
|
|
cir::FuncOp initAlias;
|
|
|
|
if (!ctorRegion.empty() || !dtorRegion.empty()) {
|
|
// Build a variable initialization function and move the initialzation code
|
|
// in the ctor region over.
|
|
cir::FuncOp f = buildCXXGlobalVarDeclInitFunc(op);
|
|
|
|
// Clear the ctor and dtor region
|
|
ctorRegion.getBlocks().clear();
|
|
dtorRegion.getBlocks().clear();
|
|
|
|
assert(!cir::MissingFeatures::astVarDeclInterface());
|
|
if (op.getTlsModel() == TLS_Model::GeneralDynamic &&
|
|
!op.getStaticLocalGuard().has_value()) {
|
|
// There are two types of global TLS variables: 'ordered' and 'unordered'.
|
|
// 'ordered' are the common case. A call to any of them causes all of the
|
|
// initializers for all other 'ordered' ones to be called, via a
|
|
// `__tls_init` function. So the 'init alias' that gets called in the
|
|
// wrapper for these goes directly to `__tls_init`.
|
|
|
|
// 'Unordered' values are the case for variable templates. In this case,
|
|
// their init alias goes directly to their init function. The FE generates
|
|
// a guard variable for them (since they cannot use the global guard), so
|
|
// we differentiate them that way.
|
|
|
|
if (op.getDynTlsRefs()->getGuardName()) {
|
|
// Unordered: the alias is the function we just generated.
|
|
initAlias = defineGlobalThreadLocalInitAlias(op, f);
|
|
} else {
|
|
// Ordered: Get the __tls_init, and make the alias to that.
|
|
initAlias = defineGlobalThreadLocalInitAlias(op, getTlsInitFn());
|
|
// Ordered inits also need to get called from the __tls_init function,
|
|
// so we add the init function to the list, so that we can add them to
|
|
// it later.
|
|
globalThreadLocalInitializers.push_back(f);
|
|
}
|
|
} else {
|
|
dynamicInitializers.push_back(f);
|
|
}
|
|
} else if (op.getTlsModel() == TLS_Model::GeneralDynamic &&
|
|
op.getDynTlsRefs() && op.isDeclaration()) {
|
|
// If this is a declaration and has no init function, we probably DO have to
|
|
// create an alias that needs checking, so create it as extern-weak.
|
|
initAlias = defineGlobalThreadLocalInitAlias(op, {});
|
|
}
|
|
|
|
// We need a wrapper for TLS globals that MIGHT have a non-constant
|
|
// initialization. The FE will have generated the DynTlsRefs for any with
|
|
// known dynamic init, or unknown (extern) init.
|
|
if (op.getTlsModel() == TLS_Model::GeneralDynamic && op.getDynTlsRefs())
|
|
defineGlobalThreadLocalWrapper(op, initAlias, !op.isDeclaration());
|
|
|
|
assert(!cir::MissingFeatures::opGlobalAnnotations());
|
|
}
|
|
|
|
void LoweringPreparePass::lowerGetGlobalOp(GetGlobalOp op) {
|
|
if (!op.getTls())
|
|
return;
|
|
auto globalOp = mlir::cast<cir::GlobalOp>(
|
|
symbolTables.lookupNearestSymbolFrom(op, op.getNameAttr()));
|
|
|
|
// Only global/namespace scope thread local variables need to have their
|
|
// get-global operations rewritten to be calls to a wrapper function. If
|
|
// we're not in a dynamic TLS (or one without the TLS markers), we can leave
|
|
// this one as a get-global and return early.
|
|
if (globalOp.getTlsModel() != TLS_Model::GeneralDynamic ||
|
|
!globalOp.getDynTlsRefs())
|
|
return;
|
|
|
|
// If this is a global TLS, we need to replace the call to 'get_global' with a
|
|
// call to the wrapper function. Classic codegen figures out some cases where
|
|
// we can omit this, but for now we're going to always put it in, as it is
|
|
// effectively a no-op.
|
|
|
|
// The first 'GetGlobalOp' at the beginning of a ctor/dtor region on one of
|
|
// these is for the purpose of creating/destroying. We want to skip replacing
|
|
// THAT one, but leave all other get-global-ops in place, else
|
|
// self-referential ops won't work right.
|
|
|
|
// Note that ctors/dtors are removed during this pass. We get away with these
|
|
// checks because the only time that these situations can actually be true
|
|
// (that is, the ctor/dtor region exist) is if we're in the process of
|
|
// converting the ctor/dtor for this. If we're NOT doing that, the ctor/dtor
|
|
// will have already disappeared.
|
|
mlir::Operation *parentOp = op->getParentOp();
|
|
if (parentOp == globalOp) {
|
|
mlir::Region *ctorRegion = &globalOp.getCtorRegion();
|
|
mlir::Region *dtorRegion = &globalOp.getDtorRegion();
|
|
|
|
if (!ctorRegion->empty() && &*ctorRegion->op_begin() == op.getOperation())
|
|
return;
|
|
if (!dtorRegion->empty() && &*dtorRegion->op_begin() == op.getOperation())
|
|
return;
|
|
}
|
|
|
|
CIRBaseBuilderTy builder(getContext());
|
|
cir::FuncOp wrapperFunc = getOrCreateThreadLocalWrapper(builder, globalOp);
|
|
|
|
builder.setInsertionPoint(op);
|
|
cir::CallOp call = builder.createCallOp(
|
|
wrapperFunc.getLoc(),
|
|
mlir::FlatSymbolRefAttr::get(wrapperFunc.getSymNameAttr()),
|
|
wrapperFunc.getFunctionType().getReturnType(), {});
|
|
op->replaceAllUsesWith(call);
|
|
op.erase();
|
|
}
|
|
|
|
void LoweringPreparePass::lowerThreeWayCmpOp(CmpThreeWayOp op) {
|
|
CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op);
|
|
|
|
mlir::Location loc = op->getLoc();
|
|
cir::CmpThreeWayInfoAttr cmpInfo = op.getInfo();
|
|
|
|
mlir::Value ltRes =
|
|
builder.getConstantInt(loc, op.getType(), cmpInfo.getLt());
|
|
mlir::Value eqRes =
|
|
builder.getConstantInt(loc, op.getType(), cmpInfo.getEq());
|
|
mlir::Value gtRes =
|
|
builder.getConstantInt(loc, op.getType(), cmpInfo.getGt());
|
|
|
|
mlir::Value transformedResult;
|
|
if (cmpInfo.getOrdering() != CmpOrdering::Partial) {
|
|
// Total ordering
|
|
mlir::Value lt =
|
|
builder.createCompare(loc, CmpOpKind::lt, op.getLhs(), op.getRhs());
|
|
mlir::Value selectOnLt = builder.createSelect(loc, lt, ltRes, gtRes);
|
|
mlir::Value eq =
|
|
builder.createCompare(loc, CmpOpKind::eq, op.getLhs(), op.getRhs());
|
|
transformedResult = builder.createSelect(loc, eq, eqRes, selectOnLt);
|
|
} else {
|
|
// Partial ordering
|
|
cir::ConstantOp unorderedRes = builder.getConstantInt(
|
|
loc, op.getType(), cmpInfo.getUnordered().value());
|
|
|
|
mlir::Value eq =
|
|
builder.createCompare(loc, CmpOpKind::eq, op.getLhs(), op.getRhs());
|
|
mlir::Value selectOnEq = builder.createSelect(loc, eq, eqRes, unorderedRes);
|
|
mlir::Value gt =
|
|
builder.createCompare(loc, CmpOpKind::gt, op.getLhs(), op.getRhs());
|
|
mlir::Value selectOnGt = builder.createSelect(loc, gt, gtRes, selectOnEq);
|
|
mlir::Value lt =
|
|
builder.createCompare(loc, CmpOpKind::lt, op.getLhs(), op.getRhs());
|
|
transformedResult = builder.createSelect(loc, lt, ltRes, selectOnGt);
|
|
}
|
|
|
|
op.replaceAllUsesWith(transformedResult);
|
|
op.erase();
|
|
}
|
|
|
|
template <typename AttributeTy>
|
|
static llvm::SmallVector<mlir::Attribute>
|
|
prepareCtorDtorAttrList(mlir::MLIRContext *context,
|
|
llvm::ArrayRef<std::pair<std::string, uint32_t>> list) {
|
|
llvm::SmallVector<mlir::Attribute> attrs;
|
|
for (const auto &[name, priority] : list)
|
|
attrs.push_back(AttributeTy::get(context, name, priority));
|
|
return attrs;
|
|
}
|
|
|
|
void LoweringPreparePass::buildGlobalCtorDtorList() {
|
|
if (!globalCtorList.empty()) {
|
|
llvm::SmallVector<mlir::Attribute> globalCtors =
|
|
prepareCtorDtorAttrList<cir::GlobalCtorAttr>(&getContext(),
|
|
globalCtorList);
|
|
|
|
mlirModule->setAttr(cir::CIRDialect::getGlobalCtorsAttrName(),
|
|
mlir::ArrayAttr::get(&getContext(), globalCtors));
|
|
}
|
|
|
|
if (!globalDtorList.empty()) {
|
|
llvm::SmallVector<mlir::Attribute> globalDtors =
|
|
prepareCtorDtorAttrList<cir::GlobalDtorAttr>(&getContext(),
|
|
globalDtorList);
|
|
mlirModule->setAttr(cir::CIRDialect::getGlobalDtorsAttrName(),
|
|
mlir::ArrayAttr::get(&getContext(), globalDtors));
|
|
}
|
|
}
|
|
|
|
cir::GlobalOp
|
|
LoweringPreparePass::createGlobalThreadLocalGuard(CIRBaseBuilderTy &builder,
|
|
mlir::Location loc) {
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
// The TLS Guard is always an Int8Ty.
|
|
cir::IntType guardTy = builder.getSIntNTy(8);
|
|
auto g = cir::GlobalOp::create(builder, loc, "__tls_guard", guardTy);
|
|
g.setLinkageAttr(cir::GlobalLinkageKindAttr::get(
|
|
builder.getContext(), cir::GlobalLinkageKind::InternalLinkage));
|
|
g.setAlignment(clang::CharUnits::One().getAsAlign().value());
|
|
// At the moment, we only have implementation for this mode, as it is the
|
|
// default. At one point we might need to load this mode from the module.
|
|
g.setTlsModel(TLS_Model::GeneralDynamic);
|
|
g.setInitialValueAttr(cir::IntAttr::get(guardTy, 0));
|
|
return g;
|
|
}
|
|
|
|
cir::IfOp LoweringPreparePass::buildGlobalTlsGuardCheck(
|
|
CIRBaseBuilderTy &builder, mlir::Location loc, cir::GlobalOp guard) {
|
|
cir::GetGlobalOp getGuard = builder.createGetGlobal(guard, /*tls=*/true);
|
|
mlir::Value getGuardValue = getGuard;
|
|
|
|
// Classic codegen always just loads the first byte of the guard instead of
|
|
// the whole thing. __tls_guard is already only 8 bits, but for the case of
|
|
// unordered TLS, it gets created as 64 bits.
|
|
if (guard.getSymType() != builder.getSIntNTy(8))
|
|
getGuardValue = builder.createBitcast(
|
|
getGuard, cir::PointerType::get(builder.getSIntNTy(8)));
|
|
|
|
mlir::Value guardLoad =
|
|
builder.createAlignedLoad(loc, getGuardValue, *guard.getAlignment());
|
|
auto zero = builder.getConstantInt(loc, builder.getSIntNTy(8), 0);
|
|
cir::CmpOp compare =
|
|
builder.createCompare(loc, cir::CmpOpKind::eq, guardLoad, zero);
|
|
return cir::IfOp::create(
|
|
builder, loc, compare,
|
|
/*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location loc) {
|
|
// Classic codegen still does this store as a i8, but it doesn't seem
|
|
// reasonable to do an i8 store into a 64 bit value?
|
|
builder.createStore(
|
|
loc, builder.getConstantInt(loc, guard.getSymType(), 1), getGuard);
|
|
});
|
|
}
|
|
|
|
void LoweringPreparePass::buildCXXGlobalTlsFunc() {
|
|
if (globalThreadLocalInitializers.empty())
|
|
return;
|
|
|
|
// The global-ordered-init function for TLS variables just calls each of the
|
|
// init-functions in order after doing a guard.
|
|
|
|
cir::FuncOp tlsInit = getTlsInitFn();
|
|
mlir::Location loc = tlsInit.getLoc();
|
|
CIRBaseBuilderTy builder(getContext());
|
|
mlir::Block *entryBB = tlsInit.addEntryBlock();
|
|
builder.setInsertionPointToStart(entryBB);
|
|
|
|
cir::IfOp ifOperation = buildGlobalTlsGuardCheck(
|
|
builder, loc, createGlobalThreadLocalGuard(builder, loc));
|
|
|
|
// Emit the body of the guarded spot.
|
|
builder.setInsertionPointToEnd(&ifOperation.getThenRegion().front());
|
|
for (cir::FuncOp initFunc : globalThreadLocalInitializers)
|
|
builder.createCallOp(loc, initFunc, {});
|
|
cir::YieldOp::create(builder, loc);
|
|
|
|
builder.setInsertionPointAfter(ifOperation);
|
|
cir::ReturnOp::create(builder, loc);
|
|
}
|
|
|
|
void LoweringPreparePass::buildCXXGlobalInitFunc() {
|
|
if (dynamicInitializers.empty())
|
|
return;
|
|
|
|
// TODO: handle globals with a user-specified initialzation priority.
|
|
// TODO: handle default priority more nicely.
|
|
assert(!cir::MissingFeatures::opGlobalCtorPriority());
|
|
|
|
SmallString<256> fnName;
|
|
cir::GlobalLinkageKind linkage;
|
|
// Include the filename in the symbol name. Including "sub_" matches gcc
|
|
// and makes sure these symbols appear lexicographically behind the symbols
|
|
// with priority (TBD). 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<clang::MangleContext> mangleCtx(
|
|
astCtx->createMangleContext());
|
|
cast<clang::ItaniumMangleContext>(*mangleCtx)
|
|
.mangleModuleInitializer(astCtx->getCurrentNamedModule(), out);
|
|
linkage = cir::GlobalLinkageKind::ExternalLinkage;
|
|
} else {
|
|
fnName += "_GLOBAL__sub_I_";
|
|
fnName += getTransformedFileName(mlirModule);
|
|
linkage = cir::GlobalLinkageKind::InternalLinkage;
|
|
}
|
|
|
|
CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointToEnd(&mlirModule.getBodyRegion().back());
|
|
auto fnType = cir::FuncType::get({}, builder.getVoidTy());
|
|
cir::FuncOp f = buildRuntimeFunction(builder, fnName, mlirModule.getLoc(),
|
|
fnType, linkage);
|
|
builder.setInsertionPointToStart(f.addEntryBlock());
|
|
for (cir::FuncOp &f : dynamicInitializers)
|
|
builder.createCallOp(f.getLoc(), f, {});
|
|
// Add the global init function (not the individual ctor functions) to the
|
|
// global ctor list.
|
|
globalCtorList.emplace_back(fnName,
|
|
cir::GlobalCtorAttr::getDefaultPriority());
|
|
|
|
cir::ReturnOp::create(builder, f.getLoc());
|
|
}
|
|
|
|
/// Lower a cir.array.ctor or cir.array.dtor into a do-while loop that
|
|
/// iterates over every element. For cir.array.ctor ops whose partial_dtor
|
|
/// region is non-empty, the ctor loop is wrapped in a cir.cleanup.scope whose
|
|
/// EH cleanup performs a reverse destruction loop using the partial dtor body.
|
|
static void lowerArrayDtorCtorIntoLoop(cir::CIRBaseBuilderTy &builder,
|
|
clang::ASTContext *astCtx,
|
|
mlir::Operation *op, mlir::Type eltTy,
|
|
mlir::Value addr,
|
|
mlir::Value numElements,
|
|
uint64_t arrayLen, bool isCtor) {
|
|
mlir::Location loc = op->getLoc();
|
|
bool isDynamic = numElements != nullptr;
|
|
|
|
// TODO: instead of getting the size from the AST context, create alias for
|
|
// PtrDiffTy and unify with CIRGen stuff.
|
|
const unsigned sizeTypeSize =
|
|
astCtx->getTypeSize(astCtx->getSignedSizeType());
|
|
|
|
// Both constructors and destructors use end = begin + numElements.
|
|
// Constructors iterate forward [begin, end). Destructors iterate backward
|
|
// from end, decrementing before calling the destructor on each element.
|
|
mlir::Value begin, end;
|
|
if (isDynamic) {
|
|
begin = addr;
|
|
end = cir::PtrStrideOp::create(builder, loc, eltTy, begin, numElements);
|
|
} else {
|
|
mlir::Value endOffsetVal =
|
|
builder.getUnsignedInt(loc, arrayLen, sizeTypeSize);
|
|
begin = cir::CastOp::create(builder, loc, eltTy,
|
|
cir::CastKind::array_to_ptrdecay, addr);
|
|
end = cir::PtrStrideOp::create(builder, loc, eltTy, begin, endOffsetVal);
|
|
}
|
|
|
|
mlir::Value start = isCtor ? begin : end;
|
|
mlir::Value stop = isCtor ? end : begin;
|
|
|
|
// For dynamic destructors, guard against zero elements.
|
|
// This places the destructor loop emitted below inside the if block.
|
|
cir::IfOp ifOp;
|
|
if (isDynamic) {
|
|
mlir::Value guardCond;
|
|
if (isCtor) {
|
|
mlir::Value zero = builder.getUnsignedInt(loc, 0, sizeTypeSize);
|
|
guardCond = cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne,
|
|
numElements, zero);
|
|
} else {
|
|
// We could check for numElements != 0 in this case too, but this matches
|
|
// what classic codegen does.
|
|
guardCond =
|
|
cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne, start, stop);
|
|
}
|
|
ifOp = cir::IfOp::create(builder, loc, guardCond,
|
|
/*withElseRegion=*/false,
|
|
[&](mlir::OpBuilder &, mlir::Location) {});
|
|
builder.setInsertionPointToStart(&ifOp.getThenRegion().front());
|
|
}
|
|
|
|
mlir::Value tmpAddr = builder.createAlloca(
|
|
loc, /*addr type*/ builder.getPointerTo(eltTy),
|
|
/*var type*/ eltTy, "__array_idx", builder.getAlignmentAttr(1));
|
|
builder.createStore(loc, start, tmpAddr);
|
|
|
|
mlir::Block *bodyBlock = &op->getRegion(0).front();
|
|
|
|
// Clone the region body (ctor/dtor call and any setup ops like per-element
|
|
// zero-init) into the loop, remapping the block argument to the current
|
|
// element pointer.
|
|
auto cloneRegionBodyInto = [&](mlir::Block *srcBlock,
|
|
mlir::Value replacement) {
|
|
mlir::IRMapping map;
|
|
map.map(srcBlock->getArgument(0), replacement);
|
|
for (mlir::Operation ®ionOp : *srcBlock) {
|
|
if (!mlir::isa<cir::YieldOp>(®ionOp))
|
|
builder.clone(regionOp, map);
|
|
}
|
|
};
|
|
|
|
mlir::Block *partialDtorBlock = nullptr;
|
|
if (auto arrayCtor = mlir::dyn_cast<cir::ArrayCtor>(op)) {
|
|
mlir::Region &partialDtor = arrayCtor.getPartialDtor();
|
|
if (!partialDtor.empty())
|
|
partialDtorBlock = &partialDtor.front();
|
|
} else if (auto arrayDtor = mlir::dyn_cast<cir::ArrayDtor>(op)) {
|
|
// When the element destructor may throw, reuse the body block as the
|
|
// partial-dtor block so that an exception thrown by an element's dtor
|
|
// continues the reverse-destruction loop in the EH cleanup region. The
|
|
// body block already stores the next element pointer to `tmpAddr`
|
|
// before invoking the dtor, so when an exception unwinds from the
|
|
// dtor call `tmpAddr` already points at the element that threw, and
|
|
// the cleanup loop picks up from `tmpAddr - 1` and walks back to
|
|
// `begin`.
|
|
if (arrayDtor.getDtorMayThrow())
|
|
partialDtorBlock = bodyBlock;
|
|
}
|
|
|
|
auto emitCtorDtorLoop = [&]() {
|
|
builder.createDoWhile(
|
|
loc,
|
|
/*condBuilder=*/
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
auto currentElement = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
|
|
auto cmp = cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne,
|
|
currentElement, stop);
|
|
builder.createCondition(cmp);
|
|
},
|
|
/*bodyBuilder=*/
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
auto currentElement = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
|
|
if (isCtor) {
|
|
cloneRegionBodyInto(bodyBlock, currentElement);
|
|
mlir::Value stride = builder.getUnsignedInt(loc, 1, sizeTypeSize);
|
|
auto nextElement = cir::PtrStrideOp::create(builder, loc, eltTy,
|
|
currentElement, stride);
|
|
builder.createStore(loc, nextElement, tmpAddr);
|
|
} else {
|
|
mlir::Value stride = builder.getSignedInt(loc, -1, sizeTypeSize);
|
|
auto prevElement = cir::PtrStrideOp::create(builder, loc, eltTy,
|
|
currentElement, stride);
|
|
builder.createStore(loc, prevElement, tmpAddr);
|
|
cloneRegionBodyInto(bodyBlock, prevElement);
|
|
}
|
|
|
|
cir::YieldOp::create(b, loc);
|
|
});
|
|
};
|
|
|
|
if (partialDtorBlock) {
|
|
cir::CleanupScopeOp::create(
|
|
builder, loc, cir::CleanupKind::EH,
|
|
/*bodyBuilder=*/
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
emitCtorDtorLoop();
|
|
cir::YieldOp::create(b, loc);
|
|
},
|
|
/*cleanupBuilder=*/
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
auto cur = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
|
|
auto cmp =
|
|
cir::CmpOp::create(builder, loc, cir::CmpOpKind::ne, cur, begin);
|
|
cir::IfOp::create(
|
|
builder, loc, cmp, /*withElseRegion=*/false,
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
builder.createDoWhile(
|
|
loc,
|
|
/*condBuilder=*/
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
auto el = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
|
|
auto neq = cir::CmpOp::create(
|
|
builder, loc, cir::CmpOpKind::ne, el, begin);
|
|
builder.createCondition(neq);
|
|
},
|
|
/*bodyBuilder=*/
|
|
[&](mlir::OpBuilder &b, mlir::Location loc) {
|
|
auto el = cir::LoadOp::create(b, loc, eltTy, tmpAddr);
|
|
mlir::Value negOne =
|
|
builder.getSignedInt(loc, -1, sizeTypeSize);
|
|
auto prev = cir::PtrStrideOp::create(builder, loc, eltTy,
|
|
el, negOne);
|
|
builder.createStore(loc, prev, tmpAddr);
|
|
cloneRegionBodyInto(partialDtorBlock, prev);
|
|
builder.createYield(loc);
|
|
});
|
|
cir::YieldOp::create(builder, loc);
|
|
});
|
|
cir::YieldOp::create(b, loc);
|
|
});
|
|
} else {
|
|
emitCtorDtorLoop();
|
|
}
|
|
|
|
if (ifOp)
|
|
cir::YieldOp::create(builder, loc);
|
|
|
|
op->erase();
|
|
}
|
|
|
|
void LoweringPreparePass::lowerArrayDtor(cir::ArrayDtor op) {
|
|
CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op.getOperation());
|
|
|
|
mlir::Type eltTy = op->getRegion(0).getArgument(0).getType();
|
|
|
|
if (op.getNumElements()) {
|
|
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
|
|
op.getNumElements(), /*arrayLen=*/0,
|
|
/*isCtor=*/false);
|
|
return;
|
|
}
|
|
|
|
auto arrayLen =
|
|
mlir::cast<cir::ArrayType>(op.getAddr().getType().getPointee()).getSize();
|
|
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
|
|
/*numElements=*/nullptr, arrayLen,
|
|
/*isCtor=*/false);
|
|
}
|
|
|
|
void LoweringPreparePass::lowerArrayCtor(cir::ArrayCtor op) {
|
|
cir::CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointAfter(op.getOperation());
|
|
|
|
mlir::Type eltTy = op->getRegion(0).getArgument(0).getType();
|
|
|
|
if (op.getNumElements()) {
|
|
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
|
|
op.getNumElements(), /*arrayLen=*/0,
|
|
/*isCtor=*/true);
|
|
return;
|
|
}
|
|
|
|
auto arrayLen =
|
|
mlir::cast<cir::ArrayType>(op.getAddr().getType().getPointee()).getSize();
|
|
lowerArrayDtorCtorIntoLoop(builder, astCtx, op, eltTy, op.getAddr(),
|
|
/*numElements=*/nullptr, arrayLen,
|
|
/*isCtor=*/true);
|
|
}
|
|
|
|
cir::FuncOp LoweringPreparePass::getCalledFunction(cir::CallOp callOp) {
|
|
mlir::SymbolRefAttr sym = llvm::dyn_cast_if_present<mlir::SymbolRefAttr>(
|
|
callOp.getCallableForCallee());
|
|
if (!sym)
|
|
return nullptr;
|
|
return symbolTables.lookupNearestSymbolFrom<cir::FuncOp>(callOp, sym);
|
|
}
|
|
|
|
void LoweringPreparePass::lowerTrivialCopyCall(cir::CallOp op) {
|
|
cir::FuncOp funcOp = getCalledFunction(op);
|
|
if (!funcOp)
|
|
return;
|
|
|
|
std::optional<cir::CtorKind> ctorKind = funcOp.getCxxConstructorKind();
|
|
if (ctorKind && *ctorKind == cir::CtorKind::Copy &&
|
|
funcOp.isCxxTrivialMemberFunction()) {
|
|
// Replace the trivial copy constructor call with a `CopyOp`
|
|
CIRBaseBuilderTy builder(getContext());
|
|
mlir::ValueRange operands = op.getOperands();
|
|
mlir::Value dest = operands[0];
|
|
mlir::Value src = operands[1];
|
|
builder.setInsertionPoint(op);
|
|
builder.createCopy(dest, src);
|
|
op.erase();
|
|
}
|
|
}
|
|
|
|
cir::GlobalOp LoweringPreparePass::getOrCreateConstAggregateGlobal(
|
|
CIRBaseBuilderTy &builder, mlir::Location loc, llvm::StringRef baseName,
|
|
mlir::Type ty, mlir::TypedAttr constant) {
|
|
// Look up (and lazily populate) the per-base-name cache.
|
|
llvm::SmallVector<cir::GlobalOp, 1> &versions =
|
|
constAggregateGlobals[baseName];
|
|
|
|
// First, check globals we've already discovered for this base name.
|
|
for (cir::GlobalOp gv : versions) {
|
|
if (gv.getSymType() == ty && gv.getInitialValue() == constant)
|
|
return gv;
|
|
}
|
|
|
|
// No cached match. Scan the module's symbol table starting from the next
|
|
// unscanned version. In practice this should usually exit on the first
|
|
// iteration, but it's possible that some other pass or a previous
|
|
// invocation of this pass created globals using this same logic.
|
|
llvm::SmallString<128> name(baseName);
|
|
size_t baseLen = name.size();
|
|
unsigned version = versions.size();
|
|
while (true) {
|
|
name.resize(baseLen);
|
|
if (version != 0) {
|
|
name.push_back('.');
|
|
llvm::Twine(version).toVector(name);
|
|
}
|
|
auto existingGv = symbolTables.lookupSymbolIn<cir::GlobalOp>(
|
|
mlirModule, mlir::StringAttr::get(&getContext(), name));
|
|
if (!existingGv)
|
|
break;
|
|
versions.push_back(existingGv);
|
|
if (existingGv.getSymType() == ty &&
|
|
existingGv.getInitialValue() == constant)
|
|
return existingGv;
|
|
++version;
|
|
}
|
|
|
|
// No match found, create a new global. The loop above found an unused name.
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
auto gv =
|
|
cir::GlobalOp::create(builder, loc, name, ty,
|
|
/*isConstant=*/true,
|
|
cir::LangAddressSpaceAttr::get(
|
|
&getContext(), cir::LangAddressSpace::Default),
|
|
cir::GlobalLinkageKind::PrivateLinkage);
|
|
mlir::SymbolTable::setSymbolVisibility(
|
|
gv, mlir::SymbolTable::Visibility::Private);
|
|
gv.setInitialValueAttr(constant);
|
|
|
|
// Keep the cached symbol table in sync with the new global so subsequent
|
|
// lookups for other base names find it.
|
|
symbolTables.getSymbolTable(mlirModule).insert(gv);
|
|
|
|
versions.push_back(gv);
|
|
return gv;
|
|
}
|
|
|
|
void LoweringPreparePass::lowerStoreOfConstAggregate(cir::StoreOp op) {
|
|
// Check if the value operand is a cir.const with aggregate type.
|
|
auto constOp = op.getValue().getDefiningOp<cir::ConstantOp>();
|
|
if (!constOp)
|
|
return;
|
|
|
|
mlir::Type ty = constOp.getType();
|
|
if (!mlir::isa<cir::ArrayType, cir::RecordType>(ty))
|
|
return;
|
|
|
|
// Only transform stores to local variables (backed by cir.alloca).
|
|
// Stores to other addresses (e.g. base_class_addr) should not be
|
|
// transformed as they may be partial initializations.
|
|
auto alloca = op.getAddr().getDefiningOp<cir::AllocaOp>();
|
|
if (!alloca)
|
|
return;
|
|
|
|
mlir::TypedAttr constant = constOp.getValue();
|
|
|
|
// OG implements several optimization tiers for constant aggregate
|
|
// initialization. For now we always create a global constant + memcpy
|
|
// (shouldCreateMemCpyFromGlobal). Future work can add the intermediate
|
|
// tiers.
|
|
assert(!cir::MissingFeatures::shouldUseBZeroPlusStoresToInitialize());
|
|
assert(!cir::MissingFeatures::shouldUseMemSetToInitialize());
|
|
assert(!cir::MissingFeatures::shouldSplitConstantStore());
|
|
|
|
// Get function name from parent cir.func.
|
|
auto func = op->getParentOfType<cir::FuncOp>();
|
|
if (!func)
|
|
return;
|
|
llvm::StringRef funcName = func.getSymName();
|
|
|
|
// Get variable name from the alloca.
|
|
llvm::StringRef varName = alloca.getName();
|
|
|
|
// Build base name: __const.<func>.<var>
|
|
std::string baseName = ("__const." + funcName + "." + varName).str();
|
|
CIRBaseBuilderTy builder(getContext());
|
|
|
|
// Check for existing globals and create a new global with a unique name
|
|
// if no match is found.
|
|
cir::GlobalOp gv = getOrCreateConstAggregateGlobal(builder, op.getLoc(),
|
|
baseName, ty, constant);
|
|
|
|
// Now replace the store with get_global + copy.
|
|
builder.setInsertionPoint(op);
|
|
|
|
auto ptrTy = cir::PointerType::get(ty);
|
|
mlir::Value globalPtr =
|
|
cir::GetGlobalOp::create(builder, op.getLoc(), ptrTy, gv.getSymName());
|
|
|
|
// Replace store with copy.
|
|
builder.createCopy(op.getAddr(), globalPtr);
|
|
|
|
// Erase the original store.
|
|
op.erase();
|
|
|
|
// Erase the cir.const if it has no remaining users.
|
|
if (constOp.use_empty())
|
|
constOp.erase();
|
|
}
|
|
|
|
void LoweringPreparePass::runOnOp(mlir::Operation *op) {
|
|
if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(op)) {
|
|
lowerArrayCtor(arrayCtor);
|
|
} else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op)) {
|
|
lowerArrayDtor(arrayDtor);
|
|
} else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) {
|
|
lowerCastOp(cast);
|
|
} else if (auto complexDiv = mlir::dyn_cast<cir::ComplexDivOp>(op)) {
|
|
lowerComplexDivOp(complexDiv);
|
|
} else if (auto complexMul = mlir::dyn_cast<cir::ComplexMulOp>(op)) {
|
|
lowerComplexMulOp(complexMul);
|
|
} else if (auto glob = mlir::dyn_cast<cir::GlobalOp>(op)) {
|
|
lowerGlobalOp(glob);
|
|
if (auto regAttr = glob->getAttrOfType<CUDAVarRegistrationInfoAttr>(
|
|
CUDAVarRegistrationInfoAttr::getMnemonic()))
|
|
cudaDeviceVars.emplace_back(glob, regAttr);
|
|
} else if (auto getGlob = mlir::dyn_cast<cir::GetGlobalOp>(op)) {
|
|
lowerGetGlobalOp(getGlob);
|
|
} else if (auto unaryOp = mlir::dyn_cast<cir::UnaryOpInterface>(op)) {
|
|
lowerUnaryOp(unaryOp);
|
|
} else if (auto callOp = dyn_cast<cir::CallOp>(op)) {
|
|
lowerTrivialCopyCall(callOp);
|
|
} else if (auto storeOp = dyn_cast<cir::StoreOp>(op)) {
|
|
lowerStoreOfConstAggregate(storeOp);
|
|
} else if (auto fnOp = dyn_cast<cir::FuncOp>(op)) {
|
|
if (auto globalCtor = fnOp.getGlobalCtorPriority())
|
|
globalCtorList.emplace_back(fnOp.getName(), globalCtor.value());
|
|
else if (auto globalDtor = fnOp.getGlobalDtorPriority())
|
|
globalDtorList.emplace_back(fnOp.getName(), globalDtor.value());
|
|
|
|
if (mlir::Attribute attr =
|
|
fnOp->getAttr(cir::CUDAKernelNameAttr::getMnemonic())) {
|
|
auto kernelNameAttr = dyn_cast<CUDAKernelNameAttr>(attr);
|
|
llvm::StringRef kernelName = kernelNameAttr.getKernelName();
|
|
cudaKernelMap[kernelName] = fnOp;
|
|
}
|
|
} else if (auto threeWayCmp = dyn_cast<cir::CmpThreeWayOp>(op)) {
|
|
lowerThreeWayCmpOp(threeWayCmp);
|
|
} else if (auto initOp = dyn_cast<cir::LocalInitOp>(op)) {
|
|
lowerLocalInitOp(initOp);
|
|
}
|
|
}
|
|
|
|
static llvm::StringRef getCUDAPrefix(clang::ASTContext *astCtx) {
|
|
if (astCtx->getLangOpts().HIP)
|
|
return "hip";
|
|
return "cuda";
|
|
}
|
|
|
|
static std::string addUnderscoredPrefix(llvm::StringRef prefix,
|
|
llvm::StringRef name) {
|
|
return ("__" + prefix + name).str();
|
|
}
|
|
|
|
/// Creates a global constructor function for the module:
|
|
///
|
|
/// For CUDA:
|
|
/// \code
|
|
/// void __cuda_module_ctor() {
|
|
/// Handle = __cudaRegisterFatBinary(GpuBinaryBlob);
|
|
/// __cuda_register_globals(Handle);
|
|
/// }
|
|
/// \endcode
|
|
///
|
|
/// For HIP:
|
|
/// \code
|
|
/// void __hip_module_ctor() {
|
|
/// if (__hip_gpubin_handle == 0) {
|
|
/// __hip_gpubin_handle = __hipRegisterFatBinary(GpuBinaryBlob);
|
|
/// __hip_register_globals(__hip_gpubin_handle);
|
|
/// }
|
|
/// }
|
|
/// \endcode
|
|
void LoweringPreparePass::buildCUDAModuleCtor() {
|
|
bool isHIP = astCtx->getLangOpts().HIP;
|
|
|
|
if (astCtx->getLangOpts().GPURelocatableDeviceCode)
|
|
llvm_unreachable("GPU RDC NYI");
|
|
|
|
// For CUDA without -fgpu-rdc, it's safe to stop generating ctor
|
|
// if there's nothing to register.
|
|
if (cudaKernelMap.empty() && cudaDeviceVars.empty())
|
|
return;
|
|
|
|
// There's no device-side binary, so no need to proceed for CUDA.
|
|
// HIP has to create an external symbol in this case, which is NYI.
|
|
mlir::Attribute cudaBinaryHandleAttr =
|
|
mlirModule->getAttr(CIRDialect::getCUDABinaryHandleAttrName());
|
|
if (!cudaBinaryHandleAttr) {
|
|
if (isHIP)
|
|
assert(!cir::MissingFeatures::hipModuleCtor());
|
|
return;
|
|
}
|
|
|
|
llvm::StringRef cudaGPUBinaryName =
|
|
mlir::cast<CUDABinaryHandleAttr>(cudaBinaryHandleAttr)
|
|
.getName()
|
|
.getValue();
|
|
|
|
llvm::vfs::FileSystem &vfs =
|
|
astCtx->getSourceManager().getFileManager().getVirtualFileSystem();
|
|
llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> gpuBinaryOrErr =
|
|
vfs.getBufferForFile(cudaGPUBinaryName);
|
|
if (std::error_code ec = gpuBinaryOrErr.getError()) {
|
|
mlirModule->emitError("cannot open GPU binary file: " + cudaGPUBinaryName +
|
|
": " + ec.message());
|
|
return;
|
|
}
|
|
std::unique_ptr<llvm::MemoryBuffer> gpuBinary =
|
|
std::move(gpuBinaryOrErr.get());
|
|
|
|
// Set up common types and builder.
|
|
llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
|
|
mlir::Location loc = mlirModule->getLoc();
|
|
CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
Type voidTy = builder.getVoidTy();
|
|
PointerType voidPtrTy = builder.getVoidPtrTy();
|
|
PointerType voidPtrPtrTy = builder.getPointerTo(voidPtrTy);
|
|
IntType intTy = builder.getSIntNTy(32);
|
|
IntType charTy = cir::IntType::get(&getContext(), astCtx->getCharWidth(),
|
|
/*isSigned=*/false);
|
|
|
|
// --- Create fatbin globals ---
|
|
|
|
// The section names are different for MAC OS X.
|
|
llvm::StringRef fatbinConstName =
|
|
astCtx->getLangOpts().HIP ? ".hip_fatbin" : ".nv_fatbin";
|
|
|
|
llvm::StringRef fatbinSectionName =
|
|
astCtx->getLangOpts().HIP ? ".hipFatBinSegment" : ".nvFatBinSegment";
|
|
|
|
// Create the fatbin string constant with GPU binary contents.
|
|
auto fatbinType =
|
|
ArrayType::get(&getContext(), charTy, gpuBinary->getBuffer().size());
|
|
std::string fatbinStrName = addUnderscoredPrefix(cudaPrefix, "_fatbin_str");
|
|
GlobalOp fatbinStr = GlobalOp::create(builder, loc, fatbinStrName, fatbinType,
|
|
/*isConstant=*/true, {},
|
|
GlobalLinkageKind::PrivateLinkage);
|
|
fatbinStr.setAlignment(8);
|
|
fatbinStr.setInitialValueAttr(cir::ConstArrayAttr::get(
|
|
fatbinType, StringAttr::get(gpuBinary->getBuffer(), fatbinType)));
|
|
fatbinStr.setSection(fatbinConstName);
|
|
fatbinStr.setPrivate();
|
|
|
|
// Create the fatbin wrapper struct:
|
|
// struct { int magic; int version; void *fatbin; void *unused; };
|
|
auto fatbinWrapperType = cir::StructType::get(
|
|
&getContext(), {intTy, intTy, voidPtrTy, voidPtrTy},
|
|
/*packed=*/false, /*padded=*/false, /*is_class=*/false);
|
|
std::string fatbinWrapperName =
|
|
addUnderscoredPrefix(cudaPrefix, "_fatbin_wrapper");
|
|
GlobalOp fatbinWrapper = GlobalOp::create(
|
|
builder, loc, fatbinWrapperName, fatbinWrapperType,
|
|
/*isConstant=*/true, {}, GlobalLinkageKind::PrivateLinkage);
|
|
fatbinWrapper.setSection(fatbinSectionName);
|
|
|
|
constexpr unsigned cudaFatMagic = 0x466243b1;
|
|
constexpr unsigned hipFatMagic = 0x48495046;
|
|
unsigned fatMagic = isHIP ? hipFatMagic : cudaFatMagic;
|
|
|
|
auto magicInit = IntAttr::get(intTy, fatMagic);
|
|
auto versionInit = IntAttr::get(intTy, 1);
|
|
auto fatbinStrSymbol =
|
|
mlir::FlatSymbolRefAttr::get(fatbinStr.getSymNameAttr());
|
|
auto fatbinInit = GlobalViewAttr::get(voidPtrTy, fatbinStrSymbol);
|
|
mlir::TypedAttr unusedInit = builder.getConstNullPtrAttr(voidPtrTy);
|
|
fatbinWrapper.setInitialValueAttr(cir::ConstRecordAttr::get(
|
|
fatbinWrapperType,
|
|
mlir::ArrayAttr::get(&getContext(),
|
|
{magicInit, versionInit, fatbinInit, unusedInit})));
|
|
|
|
// Create the GPU binary handle global variable.
|
|
std::string gpubinHandleName =
|
|
addUnderscoredPrefix(cudaPrefix, "_gpubin_handle");
|
|
|
|
GlobalOp gpuBinHandle = GlobalOp::create(
|
|
builder, loc, gpubinHandleName, voidPtrPtrTy,
|
|
/*isConstant=*/false, {}, cir::GlobalLinkageKind::InternalLinkage);
|
|
gpuBinHandle.setInitialValueAttr(builder.getConstNullPtrAttr(voidPtrPtrTy));
|
|
gpuBinHandle.setPrivate();
|
|
|
|
// Declare this function:
|
|
// void **__{cuda|hip}RegisterFatBinary(void *);
|
|
|
|
std::string regFuncName =
|
|
addUnderscoredPrefix(cudaPrefix, "RegisterFatBinary");
|
|
FuncType regFuncType = FuncType::get({voidPtrTy}, voidPtrPtrTy);
|
|
cir::FuncOp regFunc =
|
|
buildRuntimeFunction(builder, regFuncName, loc, regFuncType);
|
|
|
|
std::string moduleCtorName = addUnderscoredPrefix(cudaPrefix, "_module_ctor");
|
|
cir::FuncOp moduleCtor = buildRuntimeFunction(
|
|
builder, moduleCtorName, loc, FuncType::get({}, voidTy),
|
|
GlobalLinkageKind::InternalLinkage);
|
|
|
|
globalCtorList.emplace_back(moduleCtorName,
|
|
cir::GlobalCtorAttr::getDefaultPriority());
|
|
builder.setInsertionPointToStart(moduleCtor.addEntryBlock());
|
|
assert(!cir::MissingFeatures::opGlobalCtorPriority());
|
|
if (isHIP) {
|
|
// --- Create HIP CTOR ---
|
|
// if (__hip_gpubin_handle == nullptr)
|
|
// __hip_gpubin_handle = __hipRegisterFatBinary(&fatbinWrapper);
|
|
// __hip_register_globals(__hip_gpubin_handle);
|
|
// atexit(__hip_module_dtor);
|
|
mlir::Block *entryBlock = builder.getInsertionBlock();
|
|
mlir::Region *parent = entryBlock->getParent();
|
|
mlir::Block *ifBlock = builder.createBlock(parent);
|
|
mlir::Block *exitBlock = builder.createBlock(parent);
|
|
{
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToEnd(entryBlock);
|
|
mlir::Value handle =
|
|
builder.createLoad(loc, builder.createGetGlobal(gpuBinHandle));
|
|
auto handlePtrTy = mlir::cast<cir::PointerType>(handle.getType());
|
|
mlir::Value nullPtr = builder.getNullPtr(handlePtrTy, loc);
|
|
mlir::Value isNull =
|
|
builder.createCompare(loc, cir::CmpOpKind::eq, handle, nullPtr);
|
|
cir::BrCondOp::create(builder, loc, isNull, ifBlock, exitBlock);
|
|
}
|
|
{
|
|
// Handle is null: load the fatbin and register it.
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToStart(ifBlock);
|
|
mlir::Value wrapper = builder.createGetGlobal(fatbinWrapper);
|
|
mlir::Value fatbinVoidPtr = builder.createBitcast(wrapper, voidPtrTy);
|
|
cir::CallOp gpuBinaryHandleCall =
|
|
builder.createCallOp(loc, regFunc, fatbinVoidPtr);
|
|
mlir::Value gpuBinaryHandle = gpuBinaryHandleCall.getResult();
|
|
// Store the value back to the global `__hip_gpubin_handle`.
|
|
mlir::Value gpuBinaryHandleGlobal = builder.createGetGlobal(gpuBinHandle);
|
|
builder.createStore(loc, gpuBinaryHandle, gpuBinaryHandleGlobal);
|
|
cir::BrOp::create(builder, loc, exitBlock);
|
|
}
|
|
{
|
|
// Exit block: load the (possibly newly-registered) handle, call
|
|
// __hip_register_globals, and register the module dtor with atexit().
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToStart(exitBlock);
|
|
mlir::Value gHandle =
|
|
builder.createLoad(loc, builder.createGetGlobal(gpuBinHandle));
|
|
|
|
if (std::optional<FuncOp> regGlobal = buildCUDARegisterGlobals())
|
|
builder.createCallOp(loc, *regGlobal, gHandle);
|
|
|
|
if (std::optional<FuncOp> dtor = buildHIPModuleDtor()) {
|
|
cir::CIRBaseBuilderTy globalBuilder(getContext());
|
|
globalBuilder.setInsertionPointToStart(mlirModule.getBody());
|
|
FuncOp atexit = buildRuntimeFunction(
|
|
globalBuilder, "atexit", loc,
|
|
FuncType::get(PointerType::get(dtor->getFunctionType()), intTy));
|
|
mlir::Value dtorFunc = GetGlobalOp::create(
|
|
builder, loc, PointerType::get(dtor->getFunctionType()),
|
|
mlir::FlatSymbolRefAttr::get(dtor->getSymNameAttr()));
|
|
builder.createCallOp(loc, atexit, dtorFunc);
|
|
}
|
|
cir::ReturnOp::create(builder, loc);
|
|
}
|
|
return;
|
|
}
|
|
if (!astCtx->getLangOpts().GPURelocatableDeviceCode) {
|
|
|
|
// --- Create CUDA CTOR-DTOR ---
|
|
// Register binary with CUDA runtime. This is substantially different in
|
|
// default mode vs. separate compilation.
|
|
// Corresponding code:
|
|
// gpuBinaryHandle = __cudaRegisterFatBinary(&fatbinWrapper);
|
|
mlir::Value wrapper = builder.createGetGlobal(fatbinWrapper);
|
|
mlir::Value fatbinVoidPtr = builder.createBitcast(wrapper, voidPtrTy);
|
|
cir::CallOp gpuBinaryHandleCall =
|
|
builder.createCallOp(loc, regFunc, fatbinVoidPtr);
|
|
mlir::Value gpuBinaryHandle = gpuBinaryHandleCall.getResult();
|
|
// Store the value back to the global `__cuda_gpubin_handle`.
|
|
mlir::Value gpuBinaryHandleGlobal = builder.createGetGlobal(gpuBinHandle);
|
|
builder.createStore(loc, gpuBinaryHandle, gpuBinaryHandleGlobal);
|
|
|
|
// --- Generate __cuda_register_globals and call it ---
|
|
if (std::optional<FuncOp> regGlobal = buildCUDARegisterGlobals()) {
|
|
builder.createCallOp(loc, *regGlobal, gpuBinaryHandle);
|
|
}
|
|
|
|
// From CUDA 10.1 onwards, we must call this function to end registration:
|
|
// void __cudaRegisterFatBinaryEnd(void **fatbinHandle);
|
|
// This is CUDA-specific, so no need to use `addUnderscoredPrefix`.
|
|
if (clang::CudaFeatureEnabled(
|
|
astCtx->getTargetInfo().getSDKVersion(),
|
|
clang::CudaFeature::CUDA_USES_FATBIN_REGISTER_END)) {
|
|
cir::CIRBaseBuilderTy globalBuilder(getContext());
|
|
globalBuilder.setInsertionPointToStart(mlirModule.getBody());
|
|
FuncOp endFunc =
|
|
buildRuntimeFunction(globalBuilder, "__cudaRegisterFatBinaryEnd", loc,
|
|
FuncType::get({voidPtrPtrTy}, voidTy));
|
|
builder.createCallOp(loc, endFunc, gpuBinaryHandle);
|
|
}
|
|
} else
|
|
llvm_unreachable("GPU RDC NYI");
|
|
|
|
// Create destructor and register it with atexit() the way NVCC does it. Doing
|
|
// it during regular destructor phase worked in CUDA before 9.2 but results in
|
|
// double-free in 9.2.
|
|
if (std::optional<FuncOp> dtor = buildCUDAModuleDtor()) {
|
|
|
|
// extern "C" int atexit(void (*f)(void));
|
|
cir::CIRBaseBuilderTy globalBuilder(getContext());
|
|
globalBuilder.setInsertionPointToStart(mlirModule.getBody());
|
|
FuncOp atexit = buildRuntimeFunction(
|
|
globalBuilder, "atexit", loc,
|
|
FuncType::get(PointerType::get(dtor->getFunctionType()), intTy));
|
|
mlir::Value dtorFunc = GetGlobalOp::create(
|
|
builder, loc, PointerType::get(dtor->getFunctionType()),
|
|
mlir::FlatSymbolRefAttr::get(dtor->getSymNameAttr()));
|
|
builder.createCallOp(loc, atexit, dtorFunc);
|
|
}
|
|
cir::ReturnOp::create(builder, loc);
|
|
}
|
|
|
|
std::optional<FuncOp> LoweringPreparePass::buildCUDAModuleDtor() {
|
|
if (!mlirModule->getAttr(CIRDialect::getCUDABinaryHandleAttrName()))
|
|
return {};
|
|
|
|
llvm::StringRef prefix = getCUDAPrefix(astCtx);
|
|
|
|
VoidType voidTy = VoidType::get(&getContext());
|
|
PointerType voidPtrPtrTy = PointerType::get(PointerType::get(voidTy));
|
|
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
|
|
cir::CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
// define: void __cudaUnregisterFatBinary(void ** handle);
|
|
std::string unregisterFuncName =
|
|
addUnderscoredPrefix(prefix, "UnregisterFatBinary");
|
|
FuncOp unregisterFunc = buildRuntimeFunction(
|
|
builder, unregisterFuncName, loc, FuncType::get({voidPtrPtrTy}, voidTy));
|
|
|
|
// void __cuda_module_dtor();
|
|
// Despite the name, OG doesn't treat it as a destructor, so it shouldn't be
|
|
// put into globalDtorList. If it were a real dtor, then it would cause
|
|
// double free above CUDA 9.2. The way to use it is to manually call
|
|
// atexit() at end of module ctor.
|
|
std::string dtorName = addUnderscoredPrefix(prefix, "_module_dtor");
|
|
FuncOp dtor =
|
|
buildRuntimeFunction(builder, dtorName, loc, FuncType::get({}, voidTy),
|
|
GlobalLinkageKind::InternalLinkage);
|
|
|
|
builder.setInsertionPointToStart(dtor.addEntryBlock());
|
|
|
|
// For dtor, we only need to call:
|
|
// __cudaUnregisterFatBinary(__cuda_gpubin_handle);
|
|
|
|
std::string gpubinName = addUnderscoredPrefix(prefix, "_gpubin_handle");
|
|
GlobalOp gpubinGlobal = cast<GlobalOp>(mlirModule.lookupSymbol(gpubinName));
|
|
mlir::Value gpubinAddress = builder.createGetGlobal(gpubinGlobal);
|
|
mlir::Value gpubin = builder.createLoad(loc, gpubinAddress);
|
|
builder.createCallOp(loc, unregisterFunc, gpubin);
|
|
ReturnOp::create(builder, loc);
|
|
|
|
return dtor;
|
|
}
|
|
|
|
/// Build the HIP module dtor:
|
|
///
|
|
/// void __hip_module_dtor() {
|
|
/// if (__hip_gpubin_handle != nullptr) {
|
|
/// __hipUnregisterFatBinary(__hip_gpubin_handle);
|
|
/// __hip_gpubin_handle = nullptr;
|
|
/// }
|
|
/// }
|
|
///
|
|
/// Despite the name, OG doesn't treat this as a real destructor: putting it on
|
|
/// the dtor list would cause a double-free. It is meant to be registered via
|
|
/// atexit() at the end of the module ctor.
|
|
std::optional<FuncOp> LoweringPreparePass::buildHIPModuleDtor() {
|
|
if (!mlirModule->getAttr(CIRDialect::getCUDABinaryHandleAttrName()))
|
|
return {};
|
|
|
|
llvm::StringRef prefix = getCUDAPrefix(astCtx);
|
|
|
|
VoidType voidTy = VoidType::get(&getContext());
|
|
PointerType voidPtrPtrTy = PointerType::get(PointerType::get(voidTy));
|
|
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
|
|
cir::CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
// void __hipUnregisterFatBinary(void ** handle);
|
|
std::string unregisterFuncName =
|
|
addUnderscoredPrefix(prefix, "UnregisterFatBinary");
|
|
FuncOp unregisterFunc = buildRuntimeFunction(
|
|
builder, unregisterFuncName, loc, FuncType::get({voidPtrPtrTy}, voidTy));
|
|
|
|
std::string dtorName = addUnderscoredPrefix(prefix, "_module_dtor");
|
|
FuncOp dtor =
|
|
buildRuntimeFunction(builder, dtorName, loc, FuncType::get({}, voidTy),
|
|
GlobalLinkageKind::InternalLinkage);
|
|
|
|
std::string gpubinName = addUnderscoredPrefix(prefix, "_gpubin_handle");
|
|
GlobalOp gpuBinGlobal = cast<GlobalOp>(mlirModule.lookupSymbol(gpubinName));
|
|
|
|
mlir::Block *entryBlock = dtor.addEntryBlock();
|
|
mlir::Block *ifBlock = builder.createBlock(&dtor.getBody());
|
|
mlir::Block *exitBlock = builder.createBlock(&dtor.getBody());
|
|
|
|
mlir::OpBuilder::InsertionGuard guard(builder);
|
|
builder.setInsertionPointToEnd(entryBlock);
|
|
mlir::Value handle =
|
|
builder.createLoad(loc, builder.createGetGlobal(gpuBinGlobal));
|
|
auto handlePtrTy = mlir::cast<cir::PointerType>(handle.getType());
|
|
mlir::Value nullPtr = builder.getNullPtr(handlePtrTy, loc);
|
|
mlir::Value isNotNull =
|
|
builder.createCompare(loc, cir::CmpOpKind::ne, handle, nullPtr);
|
|
cir::BrCondOp::create(builder, loc, isNotNull, ifBlock, exitBlock);
|
|
|
|
{
|
|
// Handle is non-null: unregister and clear it.
|
|
mlir::OpBuilder::InsertionGuard ifGuard(builder);
|
|
builder.setInsertionPointToStart(ifBlock);
|
|
builder.createCallOp(loc, unregisterFunc, handle);
|
|
builder.createStore(loc, nullPtr, builder.createGetGlobal(gpuBinGlobal));
|
|
cir::BrOp::create(builder, loc, exitBlock);
|
|
}
|
|
{
|
|
mlir::OpBuilder::InsertionGuard exitGuard(builder);
|
|
builder.setInsertionPointToStart(exitBlock);
|
|
cir::ReturnOp::create(builder, loc);
|
|
}
|
|
|
|
return dtor;
|
|
}
|
|
|
|
std::optional<FuncOp> LoweringPreparePass::buildCUDARegisterGlobals() {
|
|
if (cudaKernelMap.empty() && cudaDeviceVars.empty())
|
|
return {};
|
|
|
|
cir::CIRBaseBuilderTy builder(getContext());
|
|
builder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
|
|
|
|
auto voidTy = VoidType::get(&getContext());
|
|
auto voidPtrTy = PointerType::get(voidTy);
|
|
auto voidPtrPtrTy = PointerType::get(voidPtrTy);
|
|
|
|
// Create the function:
|
|
// void __cuda_register_globals(void **fatbinHandle)
|
|
std::string regGlobalFuncName =
|
|
addUnderscoredPrefix(cudaPrefix, "_register_globals");
|
|
auto regGlobalFuncTy = FuncType::get({voidPtrPtrTy}, voidTy);
|
|
FuncOp regGlobalFunc =
|
|
buildRuntimeFunction(builder, regGlobalFuncName, loc, regGlobalFuncTy,
|
|
/*linkage=*/GlobalLinkageKind::InternalLinkage);
|
|
builder.setInsertionPointToStart(regGlobalFunc.addEntryBlock());
|
|
|
|
buildCUDARegisterGlobalFunctions(builder, regGlobalFunc);
|
|
buildCUDARegisterVars(builder, regGlobalFunc);
|
|
|
|
ReturnOp::create(builder, loc);
|
|
return regGlobalFunc;
|
|
}
|
|
|
|
void LoweringPreparePass::buildCUDARegisterGlobalFunctions(
|
|
cir::CIRBaseBuilderTy &builder, FuncOp regGlobalFunc) {
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
|
|
cir::CIRDataLayout dataLayout(mlirModule);
|
|
|
|
auto voidTy = VoidType::get(&getContext());
|
|
auto voidPtrTy = PointerType::get(voidTy);
|
|
auto voidPtrPtrTy = PointerType::get(voidPtrTy);
|
|
IntType intTy = builder.getSIntNTy(32);
|
|
IntType charTy = cir::IntType::get(&getContext(), astCtx->getCharWidth(),
|
|
/*isSigned=*/false);
|
|
|
|
// Extract the GPU binary handle argument.
|
|
mlir::Value fatbinHandle = *regGlobalFunc.args_begin();
|
|
|
|
cir::CIRBaseBuilderTy globalBuilder(getContext());
|
|
globalBuilder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
// Declare CUDA internal functions:
|
|
// int __cudaRegisterFunction(
|
|
// void **fatbinHandle,
|
|
// const char *hostFunc,
|
|
// char *deviceFunc,
|
|
// const char *deviceName,
|
|
// int threadLimit,
|
|
// uint3 *tid, uint3 *bid, dim3 *bDim, dim3 *gDim,
|
|
// int *wsize
|
|
// )
|
|
// OG doesn't care about the types at all. They're treated as void*.
|
|
|
|
FuncOp cudaRegisterFunction = buildRuntimeFunction(
|
|
globalBuilder, addUnderscoredPrefix(cudaPrefix, "RegisterFunction"), loc,
|
|
FuncType::get({voidPtrPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, intTy,
|
|
voidPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, voidPtrTy},
|
|
intTy));
|
|
|
|
auto makeConstantString = [&](llvm::StringRef str) -> GlobalOp {
|
|
auto strType = ArrayType::get(&getContext(), charTy, 1 + str.size());
|
|
auto tmpString = cir::GlobalOp::create(
|
|
globalBuilder, loc, (".str" + str).str(), strType,
|
|
/*isConstant=*/true, {},
|
|
/*linkage=*/cir::GlobalLinkageKind::PrivateLinkage);
|
|
|
|
// We must make the string zero-terminated.
|
|
tmpString.setInitialValueAttr(
|
|
ConstArrayAttr::get(strType, StringAttr::get(str + "\0", strType)));
|
|
tmpString.setPrivate();
|
|
return tmpString;
|
|
};
|
|
|
|
cir::ConstantOp cirNullPtr = builder.getNullPtr(voidPtrTy, loc);
|
|
bool isHIP = astCtx->getLangOpts().HIP;
|
|
for (auto kernelName : cudaKernelMap.keys()) {
|
|
FuncOp deviceStub = cudaKernelMap[kernelName];
|
|
GlobalOp deviceFuncStr = makeConstantString(kernelName);
|
|
mlir::Value deviceFunc = builder.createBitcast(
|
|
builder.createGetGlobal(deviceFuncStr), voidPtrTy);
|
|
|
|
mlir::Value hostFunc;
|
|
if (isHIP) {
|
|
// Under HIP, the kernel-handle is a GlobalOp shadow created by CIR
|
|
// codegen and named with the kernel-reference mangled name (e.g.
|
|
// `@_Z2fnv` pointing at the device-stub function
|
|
// `_Z17__device_stub__fnv`). The CUDAKernelNameAttr on the device-stub
|
|
// uses the same name, so we can resolve the shadow by symbol lookup.
|
|
auto funcHandle = cast<GlobalOp>(mlirModule.lookupSymbol(kernelName));
|
|
hostFunc =
|
|
builder.createBitcast(builder.createGetGlobal(funcHandle), voidPtrTy);
|
|
} else {
|
|
hostFunc = builder.createBitcast(
|
|
GetGlobalOp::create(
|
|
builder, loc, PointerType::get(deviceStub.getFunctionType()),
|
|
mlir::FlatSymbolRefAttr::get(deviceStub.getSymNameAttr())),
|
|
voidPtrTy);
|
|
}
|
|
builder.createCallOp(
|
|
loc, cudaRegisterFunction,
|
|
{fatbinHandle, hostFunc, deviceFunc, deviceFunc,
|
|
ConstantOp::create(builder, loc, IntAttr::get(intTy, -1)), cirNullPtr,
|
|
cirNullPtr, cirNullPtr, cirNullPtr, cirNullPtr});
|
|
}
|
|
}
|
|
|
|
// Emit `__{cuda|hip}RegisterVar` calls inside `__{cuda|hip}_register_globals`
|
|
// for every device-side shadow that carries a `cu.var_registration` attribute
|
|
// (attached by `CIRGenNVCUDARuntime::handleVarRegistration`).
|
|
void LoweringPreparePass::buildCUDARegisterVars(cir::CIRBaseBuilderTy &builder,
|
|
FuncOp regGlobalFunc) {
|
|
mlir::Location loc = mlirModule.getLoc();
|
|
llvm::StringRef cudaPrefix = getCUDAPrefix(astCtx);
|
|
cir::CIRDataLayout dataLayout(mlirModule);
|
|
|
|
PointerType voidPtrTy = builder.getVoidPtrTy();
|
|
PointerType voidPtrPtrTy = builder.getPointerTo(voidPtrTy);
|
|
IntType intTy = builder.getSIntNTy(32);
|
|
IntType sizeTy =
|
|
builder.getUIntNTy(astCtx->getTargetInfo().getMaxPointerWidth());
|
|
IntType charTy = cir::IntType::get(&getContext(), astCtx->getCharWidth(),
|
|
/*isSigned=*/false);
|
|
|
|
if (cudaDeviceVars.empty())
|
|
return;
|
|
|
|
cir::CIRBaseBuilderTy globalBuilder(getContext());
|
|
globalBuilder.setInsertionPointToStart(mlirModule.getBody());
|
|
|
|
// void __{cuda|hip}RegisterVar(void **fatbinHandle,
|
|
// char *hostVar, char *deviceAddress,
|
|
// const char *deviceName, int ext,
|
|
// size_t size, int constant, int normalized);
|
|
// OG ignores parameter types, treating pointers as void*.
|
|
cir::VoidType voidTy = builder.getVoidTy();
|
|
FuncOp cudaRegisterVar = buildRuntimeFunction(
|
|
globalBuilder, addUnderscoredPrefix(cudaPrefix, "RegisterVar"), loc,
|
|
FuncType::get({voidPtrPtrTy, voidPtrTy, voidPtrTy, voidPtrTy, intTy,
|
|
sizeTy, intTy, intTy},
|
|
voidTy));
|
|
|
|
auto makeConstantString = [&](llvm::StringRef str) -> GlobalOp {
|
|
auto strType = ArrayType::get(&getContext(), charTy, 1 + str.size());
|
|
auto tmpString = cir::GlobalOp::create(
|
|
globalBuilder, loc, (".str" + str).str(), strType,
|
|
/*isConstant=*/true, {},
|
|
/*linkage=*/cir::GlobalLinkageKind::PrivateLinkage);
|
|
tmpString.setInitialValueAttr(
|
|
ConstArrayAttr::get(strType, StringAttr::get(str + "\0", strType)));
|
|
tmpString.setPrivate();
|
|
return tmpString;
|
|
};
|
|
|
|
mlir::Value fatbinHandle = *regGlobalFunc.args_begin();
|
|
|
|
for (auto &[global, regAttr] : cudaDeviceVars) {
|
|
switch (regAttr.getKind()) {
|
|
case cir::CUDADeviceVarKind::Variable:
|
|
break;
|
|
case cir::CUDADeviceVarKind::Surface:
|
|
llvm_unreachable("Surface registration NYI");
|
|
case cir::CUDADeviceVarKind::Texture:
|
|
llvm_unreachable("Texture registration NYI");
|
|
}
|
|
|
|
if (regAttr.getIsManaged())
|
|
llvm_unreachable("Managed variable registration NYI");
|
|
|
|
GlobalOp deviceNameStr = makeConstantString(regAttr.getDeviceSideName());
|
|
mlir::Value deviceName = builder.createBitcast(
|
|
builder.createGetGlobal(deviceNameStr), voidPtrTy);
|
|
mlir::Value hostVar =
|
|
builder.createBitcast(builder.createGetGlobal(global), voidPtrTy);
|
|
|
|
auto isExtern = ConstantOp::create(
|
|
builder, loc, IntAttr::get(intTy, regAttr.getIsExtern() ? 1 : 0));
|
|
llvm::TypeSize size = dataLayout.getTypeAllocSize(global.getSymType());
|
|
auto varSize = ConstantOp::create(
|
|
builder, loc, IntAttr::get(sizeTy, size.getFixedValue()));
|
|
auto isConstant = ConstantOp::create(
|
|
builder, loc, IntAttr::get(intTy, regAttr.getIsConstant() ? 1 : 0));
|
|
auto normalized = ConstantOp::create(builder, loc, IntAttr::get(intTy, 0));
|
|
builder.createCallOp(loc, cudaRegisterVar,
|
|
{fatbinHandle, hostVar, deviceName, deviceName,
|
|
isExtern, varSize, isConstant, normalized});
|
|
}
|
|
}
|
|
|
|
void LoweringPreparePass::runOnOperation() {
|
|
mlir::Operation *op = getOperation();
|
|
if (isa<::mlir::ModuleOp>(op))
|
|
mlirModule = cast<::mlir::ModuleOp>(op);
|
|
|
|
llvm::SmallVector<mlir::Operation *> opsToTransform;
|
|
|
|
op->walk([&](mlir::Operation *op) {
|
|
if (mlir::isa<cir::ArrayCtor, cir::ArrayDtor, cir::CastOp,
|
|
cir::ComplexMulOp, cir::ComplexDivOp, cir::DynamicCastOp,
|
|
cir::FuncOp, cir::CallOp, cir::GetGlobalOp, cir::GlobalOp,
|
|
cir::StoreOp, cir::CmpThreeWayOp, cir::IncOp, cir::DecOp,
|
|
cir::MinusOp, cir::NotOp, cir::LocalInitOp>(op))
|
|
opsToTransform.push_back(op);
|
|
});
|
|
|
|
for (mlir::Operation *o : opsToTransform)
|
|
runOnOp(o);
|
|
|
|
buildCXXGlobalInitFunc();
|
|
buildCXXGlobalTlsFunc();
|
|
if (astCtx->getLangOpts().CUDA && !astCtx->getLangOpts().CUDAIsDevice)
|
|
buildCUDAModuleCtor();
|
|
|
|
buildGlobalCtorDtorList();
|
|
}
|
|
|
|
std::unique_ptr<Pass> mlir::createLoweringPreparePass() {
|
|
return std::make_unique<LoweringPreparePass>();
|
|
}
|
|
|
|
std::unique_ptr<Pass>
|
|
mlir::createLoweringPreparePass(clang::ASTContext *astCtx) {
|
|
auto pass = std::make_unique<LoweringPreparePass>();
|
|
pass->setASTContext(astCtx);
|
|
return std::move(pass);
|
|
}
|