Skip to content

Commit

Permalink
[CIR][CodeGen] Emit globals with constructor initializer (#197)
Browse files Browse the repository at this point in the history
This change does the CIR generation for globals initialized by a
constructor call. It currently only covers C++ to CIR generation. The
corresponding LLVM lowering will be in a follow-up commit.

A motivating example is

```
class Init {
  friend class ios_base;

public:
  Init(bool);
  ~Init();

private:
  static bool _S_synced_with_stdio;
};

static Init ioinit(true);
```

Unlike what the default Clang codegen generates LLVM that detaches the
initialization code from the global var definition (like below), we are
taking a different approach that keeps them together, which we think
will make the later dataflow analysis/transform easier.

```
@_ZL8ioinit = internal global %class.Init zeroinitializer, align 1, !dbg !0

define internal void @cxx_global_var_init() #0 section ".text.startup" !dbg !23 {
entry:
  call void @_ZN4InitC2Ev(ptr noundef nonnull align 1 dereferenceable(1) @_ZL8ioinit), !dbg !27
  %0 = call i32 @cxa_atexit(ptr @_ZN4InitD1Ev, ptr @_ZL8ioinit, ptr @dso_handle) #3, !dbg !29
  ret void, !dbg !27
}
```

So on CIR, we have something like:

```
  cir.global "private" internal @_ZL8__ioinit = ctor : !ty_22class2EInit22 {
    %0 = cir.get_global @_ZL8__ioinit : cir.ptr <!ty_22class2EInit22> loc(#loc8)
    %1 = cir.const(#true) : !cir.bool loc(#loc5)
    cir.call @_ZN4InitC1Eb(%0, %1) : (!cir.ptr<!ty_22class2EInit22>, !cir.bool) -> () loc(#loc6)
}
```

The destructor support will also be in a separate change.
  • Loading branch information
htyu authored and lanza committed Jan 29, 2024
1 parent 982201f commit e6b36ff
Show file tree
Hide file tree
Showing 12 changed files with 291 additions and 55 deletions.
18 changes: 10 additions & 8 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -571,11 +571,12 @@ def YieldOpKind : I32EnumAttr<

def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
ParentOneOf<["IfOp", "ScopeOp", "SwitchOp", "LoopOp", "AwaitOp",
"TernaryOp"]>]> {
"TernaryOp", "GlobalOp"]>]> {
let summary = "Terminate CIR regions";
let description = [{
The `cir.yield` operation terminates regions on different CIR operations:
`cir.if`, `cir.scope`, `cir.switch`, `cir.loop`, `cir.await` and `cir.ternary`.
`cir.if`, `cir.scope`, `cir.switch`, `cir.loop`, `cir.await`, `cir.ternary`
and `cir.global`.

Might yield an SSA value and the semantics of how the values are yielded is
defined by the parent operation.
Expand Down Expand Up @@ -1242,7 +1243,7 @@ def SignedOverflowBehaviorEnum : I32EnumAttr<
}


def GlobalOp : CIR_Op<"global", [Symbol]> {
def GlobalOp : CIR_Op<"global", [Symbol, DeclareOpInterfaceMethods<RegionBranchOpInterface>, NoRegionArguments]> {
let summary = "Declares or defines a global variable";
let description = [{
The `cir.global` operation declares or defines a named global variable.
Expand Down Expand Up @@ -1280,19 +1281,19 @@ def GlobalOp : CIR_Op<"global", [Symbol]> {
OptionalAttr<AnyAttr>:$initial_value,
UnitAttr:$constant,
OptionalAttr<I64Attr>:$alignment);

let regions = (region AnyRegion:$ctorRegion);
let assemblyFormat = [{
($sym_visibility^)?
(`constant` $constant^)?
$linkage
$sym_name
custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value)
custom<GlobalOpTypeAndInitialValue>($sym_type, $initial_value, $ctorRegion)
attr-dict
}];

let extraClassDeclaration = [{
bool isDeclaration() {
return !getInitialValue();
return !getInitialValue() && getCtorRegion().empty();
}
bool hasInitializer() { return !isDeclaration(); }
bool hasAvailableExternallyLinkage() {
Expand All @@ -1318,8 +1319,9 @@ def GlobalOp : CIR_Op<"global", [Symbol]> {
CArg<"bool", "false">:$isConstant,
// CIR defaults to external linkage.
CArg<"cir::GlobalLinkageKind",
"cir::GlobalLinkageKind::ExternalLinkage">:$linkage
)>
"cir::GlobalLinkageKind::ExternalLinkage">:$linkage,
CArg<"function_ref<void(OpBuilder &, Location)>",
"nullptr">:$ctorBuilder)>
];

let hasVerifier = 1;
Expand Down
45 changes: 45 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,34 @@
using namespace clang;
using namespace cir;

static void buildDeclInit(CIRGenFunction &CGF, const VarDecl *D,
Address DeclPtr) {
assert((D->hasGlobalStorage() ||
(D->hasLocalStorage() &&
CGF.getContext().getLangOpts().OpenCLCPlusPlus)) &&
"VarDecl must have global or local (in the case of OpenCL) storage!");
assert(!D->getType()->isReferenceType() &&
"Should not call buildDeclInit on a reference!");

QualType type = D->getType();
LValue lv = CGF.makeAddrLValue(DeclPtr, type);

const Expr *Init = D->getInit();
switch (CIRGenFunction::getEvaluationKind(type)) {
case TEK_Aggregate:
CGF.buildAggExpr(
Init, AggValueSlot::forLValue(lv, AggValueSlot::IsDestructed,
AggValueSlot::DoesNotNeedGCBarriers,
AggValueSlot::IsNotAliased,
AggValueSlot::DoesNotOverlap));
return;
case TEK_Scalar:
llvm_unreachable("scalar evaluation NYI");
case TEK_Complex:
llvm_unreachable("complext evaluation NYI");
}
}

mlir::cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl GD) {
const auto &FnInfo = getTypes().arrangeCXXStructorDeclaration(GD);
auto Fn = getAddrOfCXXStructor(GD, &FnInfo, /*FnType=*/nullptr,
Expand All @@ -38,3 +66,20 @@ mlir::cir::FuncOp CIRGenModule::codegenCXXStructor(GlobalDecl GD) {
// TODO: SetLLVMFunctionAttributesForDefinition
return Fn;
}

void CIRGenModule::codegenGlobalInitCxxStructor(const VarDecl *D,
mlir::cir::GlobalOp Addr) {
CIRGenFunction CGF{*this, builder, true};
CurCGF = &CGF;
CurCGF->CurFn = Addr;
{
mlir::OpBuilder::InsertionGuard guard(builder);
auto block = builder.createBlock(&Addr.getCtorRegion());
builder.setInsertionPointToStart(block);
Address DeclAddr(getAddrOfGlobalVar(D), getASTContext().getDeclAlign(D));
buildDeclInit(CGF, D, DeclAddr);
builder.setInsertionPointToEnd(block);
builder.create<mlir::cir::YieldOp>(Addr->getLoc());
}
CurCGF = nullptr;
}
4 changes: 3 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenCoroutine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ CIRGenFunction::buildCoroutineBody(const CoroutineBodyStmt &S) {
auto openCurlyLoc = getLoc(S.getBeginLoc());
auto nullPtrCst = builder.getNullPtr(VoidPtrTy, openCurlyLoc);

CurFn.setCoroutineAttr(mlir::UnitAttr::get(builder.getContext()));
auto Fn = dyn_cast<mlir::cir::FuncOp>(CurFn);
assert(Fn && "other callables NYI");
Fn.setCoroutineAttr(mlir::UnitAttr::get(builder.getContext()));
auto coroId = buildCoroIDBuiltinCall(openCurlyLoc, nullPtrCst);
createCoroData(*this, CurCoro, coroId);

Expand Down
51 changes: 50 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenDeclCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "CIRGenFunction.h"
#include "CIRGenModule.h"
#include "TargetInfo.h"
#include "clang/AST/Attr.h"
Expand All @@ -28,4 +29,52 @@ void CIRGenModule::buildCXXGlobalInitFunc() {
return;

assert(0 && "NYE");
}
}

void CIRGenModule::buildGlobalVarDeclInit(const VarDecl *D,
mlir::cir::GlobalOp Addr,
bool PerformInit) {
// According to E.2.3.1 in CUDA-7.5 Programming guide: __device__,
// __constant__ and __shared__ variables defined in namespace scope,
// that are of class type, cannot have a non-empty constructor. All
// the checks have been done in Sema by now. Whatever initializers
// are allowed are empty and we just need to ignore them here.
if (getLangOpts().CUDAIsDevice && !getLangOpts().GPUAllowDeviceInit &&
(D->hasAttr<CUDADeviceAttr>() || D->hasAttr<CUDAConstantAttr>() ||
D->hasAttr<CUDASharedAttr>()))
return;

assert(!getLangOpts().OpenMP && "OpenMP global var init not implemented");

// Check if we've already initialized this decl.
auto I = DelayedCXXInitPosition.find(D);
if (I != DelayedCXXInitPosition.end() && I->second == ~0U)
return;

if (PerformInit) {
QualType T = D->getType();

// TODO: handle address space
// The address space of a static local variable (DeclPtr) may be different
// from the address space of the "this" argument of the constructor. In that
// case, we need an addrspacecast before calling the constructor.
//
// struct StructWithCtor {
// __device__ StructWithCtor() {...}
// };
// __device__ void foo() {
// __shared__ StructWithCtor s;
// ...
// }
//
// For example, in the above CUDA code, the static local variable s has a
// "shared" address space qualifier, but the constructor of StructWithCtor
// expects "this" in the "generic" address space.
assert(!UnimplementedFeature::addressSpace());

if (!T->isReferenceType()) {
codegenGlobalInitCxxStructor(D, Addr);
return;
}
}
}
11 changes: 7 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,8 @@ static CIRGenCallee buildDirectCallee(CIRGenModule &CGM, GlobalDecl GD) {

// When directing calling an inline builtin, call it through it's mangled
// name to make it clear it's not the actual builtin.
if (CGF.CurFn.getName() != FDInlineName &&
auto Fn = cast<mlir::cir::FuncOp>(CGF.CurFn);
if (Fn.getName() != FDInlineName &&
onlyHasInlineBuiltinDeclaration(FD)) {
assert(0 && "NYI");
}
Expand Down Expand Up @@ -2134,7 +2135,7 @@ mlir::Value CIRGenFunction::buildAlloca(StringRef name, mlir::Type ty,
mlir::Location loc, CharUnits alignment,
bool insertIntoFnEntryBlock) {
mlir::Block *entryBlock = insertIntoFnEntryBlock
? &CurFn.getRegion().front()
? getCurFunctionEntryBlock()
: currLexScope->getEntryBlock();
return buildAlloca(name, ty, loc, alignment,
builder.getBestAllocaInsertPoint(entryBlock));
Expand Down Expand Up @@ -2512,9 +2513,11 @@ mlir::Value CIRGenFunction::buildScalarConstant(
}

LValue CIRGenFunction::buildPredefinedLValue(const PredefinedExpr *E) {
auto SL = E->getFunctionName();
const auto *SL = E->getFunctionName();
assert(SL != nullptr && "No StringLiteral name in PredefinedExpr");
StringRef FnName = CurFn.getName();
auto Fn = dyn_cast<mlir::cir::FuncOp>(CurFn);
assert(Fn && "other callables NYI");
StringRef FnName = Fn.getName();
if (FnName.starts_with("\01"))
FnName = FnName.substr(1);
StringRef NameItems[] = {PredefinedExpr::getIdentKindName(E->getIdentKind()),
Expand Down
8 changes: 6 additions & 2 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,9 @@ void CIRGenFunction::LexicalScopeGuard::cleanup() {

auto buildReturn = [&](mlir::Location loc) {
// If we are on a coroutine, add the coro_end builtin call.
if (CGF.CurFn.getCoroutine())
auto Fn = dyn_cast<mlir::cir::FuncOp>(CGF.CurFn);
assert(Fn && "other callables NYI");
if (Fn.getCoroutine())
CGF.buildCoroEndBuiltinCall(
loc, builder.getNullPtr(builder.getVoidPtrTy(), loc));

Expand Down Expand Up @@ -1009,7 +1011,9 @@ void CIRGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
const auto *MD = cast<CXXMethodDecl>(D);
if (MD->getParent()->isLambda() && MD->getOverloadedOperator() == OO_Call) {
// We're in a lambda.
CurFn.setLambdaAttr(mlir::UnitAttr::get(builder.getContext()));
auto Fn = dyn_cast<mlir::cir::FuncOp>(CurFn);
assert(Fn && "other callables NYI");
Fn.setLambdaAttr(mlir::UnitAttr::get(builder.getContext()));

// Figure out the captures.
MD->getParent()->getCaptureFields(LambdaCaptureFields,
Expand Down
10 changes: 9 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,9 @@ class CIRGenFunction : public CIRGenTypeCache {
const clang::Decl *CurCodeDecl;
const CIRGenFunctionInfo *CurFnInfo;
clang::QualType FnRetTy;
mlir::cir::FuncOp CurFn = nullptr;

/// This is the current function or global initializer that is generated code for.
mlir::Operation *CurFn = nullptr;

/// Save Parameter Decl for coroutine.
llvm::SmallVector<const ParmVarDecl *, 4> FnArgs;
Expand All @@ -591,6 +593,12 @@ class CIRGenFunction : public CIRGenTypeCache {

CIRGenModule &getCIRGenModule() { return CGM; }

mlir::Block* getCurFunctionEntryBlock() {
auto Fn = dyn_cast<mlir::cir::FuncOp>(CurFn);
assert(Fn && "other callables NYI");
return &Fn.getRegion().front();
}

/// Sanitizers enabled for this function.
clang::SanitizerSet SanOpts;

Expand Down
30 changes: 24 additions & 6 deletions clang/lib/CIR/CodeGen/CIRGenModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ mlir::cir::GlobalOp CIRGenModule::createGlobalOp(CIRGenModule &CGM,
// Be sure to insert global before the current function
auto *curCGF = CGM.getCurrCIRGenFun();
if (curCGF)
builder.setInsertionPoint(curCGF->CurFn.getOperation());
builder.setInsertionPoint(curCGF->CurFn);

g = builder.create<mlir::cir::GlobalOp>(loc, name, t, isCst);
if (!curCGF)
Expand Down Expand Up @@ -783,8 +783,14 @@ void CIRGenModule::buildGlobalVarDefinition(const clang::VarDecl *D,
// TODO(cir): LLVM's codegen uses a llvm::TrackingVH here. Is that
// necessary here for CIR gen?
mlir::Attribute Init;
// TODO(cir): bool NeedsGlobalCtor = false;
bool NeedsGlobalCtor = false;
// Whether the definition of the variable is available externally.
// If yes, we shouldn't emit the GloablCtor and GlobalDtor for the variable
// since this is the job for its original source.
bool IsDefinitionAvailableExternally =
astCtx.GetGVALinkageForVariable(D) == GVA_AvailableExternally;
bool NeedsGlobalDtor =
!IsDefinitionAvailableExternally &&
D->needsDestruction(astCtx) == QualType::DK_cxx_destructor;

const VarDecl *InitDecl;
Expand Down Expand Up @@ -830,7 +836,19 @@ void CIRGenModule::buildGlobalVarDefinition(const clang::VarDecl *D,
emitter.emplace(*this);
auto Initializer = emitter->tryEmitForInitializer(*InitDecl);
if (!Initializer) {
assert(0 && "not implemented");
QualType T = InitExpr->getType();
if (D->getType()->isReferenceType())
T = D->getType();

if (getLangOpts().CPlusPlus) {
if (InitDecl->hasFlexibleArrayInit(astCtx))
ErrorUnsupported(D, "flexible array initializer");
Init = builder.getZeroInitAttr(getCIRType(T));
if (!IsDefinitionAvailableExternally)
NeedsGlobalCtor = true;
} else {
ErrorUnsupported(D, "static initializer");
}
} else {
Init = Initializer;
// We don't need an initializer, so remove the entry for the delayed
Expand Down Expand Up @@ -972,8 +990,8 @@ void CIRGenModule::buildGlobalVarDefinition(const clang::VarDecl *D,

// TODO(cir):
// Emit the initializer function if necessary.
// if (NeedsGlobalCtor || NeedsGlobalDtor)
// EmitCXXGlobalVarDeclInitFunc(D, GV, NeedsGlobalCtor);
if (NeedsGlobalCtor || NeedsGlobalDtor)
buildGlobalVarDeclInit(D, GV, NeedsGlobalCtor);

// TODO(cir): sanitizers (reportGlobalToASan) and global variable debug
// information.
Expand Down Expand Up @@ -1788,7 +1806,7 @@ CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
// Be sure to insert a new function before a current one.
auto *curCGF = getCurrCIRGenFun();
if (curCGF)
builder.setInsertionPoint(curCGF->CurFn.getOperation());
builder.setInsertionPoint(curCGF->CurFn);

f = builder.create<mlir::cir::FuncOp>(loc, name, Ty);

Expand Down
8 changes: 8 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@ class CIRGenModule : public CIRGenTypeCache {
void buildGlobalVarDefinition(const clang::VarDecl *D,
bool IsTentative = false);

/// Emit the function that initializes the specified global
void buildGlobalVarDeclInit(const VarDecl *D, mlir::cir::GlobalOp Addr,
bool PerformInit);

void addDeferredVTable(const CXXRecordDecl *RD) {
DeferredVTables.push_back(RD);
}
Expand Down Expand Up @@ -508,6 +512,10 @@ class CIRGenModule : public CIRGenTypeCache {
// or if they are alias to each other.
mlir::cir::FuncOp codegenCXXStructor(clang::GlobalDecl GD);

// Produce code for this constructor/destructor for global initialzation.
void codegenGlobalInitCxxStructor(const clang::VarDecl *D,
mlir::cir::GlobalOp Addr);

bool lookupRepresentativeDecl(llvm::StringRef MangledName,
clang::GlobalDecl &Result) const;

Expand Down
Loading

0 comments on commit e6b36ff

Please sign in to comment.