Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,22 @@ def FuncOp : CIR_Op<"func", [
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// UnreachableOp
//===----------------------------------------------------------------------===//

def UnreachableOp : CIR_Op<"unreachable", [Terminator]> {
let summary = "invoke immediate undefined behavior";
let description = [{
If the program control flow reaches a `cir.unreachable` operation, the
program exhibits undefined behavior immediately. This operation is useful
in cases where the unreachability of a program point needs to be explicitly
marked.
}];

let assemblyFormat = "attr-dict";
}

//===----------------------------------------------------------------------===//
// TrapOp
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ struct MissingFeatures {
static bool astVarDeclInterface() { return false; }
static bool stackSaveOp() { return false; }
static bool aggValueSlot() { return false; }
static bool generateDebugInfo() { return false; }

static bool fpConstraints() { return false; }
static bool sanitizers() { return false; }
Expand Down
219 changes: 178 additions & 41 deletions clang/lib/CIR/CodeGen/CIRGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,13 @@ mlir::Location CIRGenFunction::getLoc(mlir::Location lhs, mlir::Location rhs) {
return mlir::FusedLoc::get(locs, metadata, &getMLIRContext());
}

void CIRGenFunction::emitAndUpdateRetAlloca(QualType type, mlir::Location loc,
CharUnits alignment) {
if (!type->isVoidType()) {
fnRetAlloca = emitAlloca("__retval", convertType(type), loc, alignment);
}
}

void CIRGenFunction::declare(mlir::Value addrVal, const Decl *var, QualType ty,
mlir::Location loc, CharUnits alignment,
bool isParam) {
Expand All @@ -149,14 +156,125 @@ void CIRGenFunction::declare(mlir::Value addrVal, const Decl *var, QualType ty,
allocaOp.setConstantAttr(mlir::UnitAttr::get(&getMLIRContext()));
}

void CIRGenFunction::LexicalScope::cleanup() {
CIRGenBuilderTy &builder = cgf.builder;
LexicalScope *localScope = cgf.curLexScope;

if (returnBlock != nullptr) {
// Write out the return block, which loads the value from `__retval` and
// issues the `cir.return`.
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToEnd(returnBlock);
(void)emitReturn(*returnLoc);
}

mlir::Block *curBlock = builder.getBlock();
if (isGlobalInit() && !curBlock)
return;
if (curBlock->mightHaveTerminator() && curBlock->getTerminator())
return;

// Get rid of any empty block at the end of the scope.
bool entryBlock = builder.getInsertionBlock()->isEntryBlock();
if (!entryBlock && curBlock->empty()) {
curBlock->erase();
if (returnBlock != nullptr && returnBlock->getUses().empty())
returnBlock->erase();
return;
}

// Reached the end of the scope.
{
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointToEnd(curBlock);

if (localScope->depth == 0) {
// Reached the end of the function.
if (returnBlock != nullptr) {
if (returnBlock->getUses().empty())
returnBlock->erase();
else {
builder.create<cir::BrOp>(*returnLoc, returnBlock);
return;
}
}
emitImplicitReturn();
return;
}
// Reached the end of a non-function scope. Some scopes, such as those
// used with the ?: operator, can return a value.
if (!localScope->isTernary() && !curBlock->mightHaveTerminator()) {
!retVal ? builder.create<cir::YieldOp>(localScope->endLoc)
: builder.create<cir::YieldOp>(localScope->endLoc, retVal);
}
}
}

cir::ReturnOp CIRGenFunction::LexicalScope::emitReturn(mlir::Location loc) {
CIRGenBuilderTy &builder = cgf.getBuilder();

if (!cgf.curFn.getFunctionType().hasVoidReturn()) {
// Load the value from `__retval` and return it via the `cir.return` op.
auto value = builder.create<cir::LoadOp>(
loc, cgf.curFn.getFunctionType().getReturnType(), *cgf.fnRetAlloca);
return builder.create<cir::ReturnOp>(loc,
llvm::ArrayRef(value.getResult()));
}
return builder.create<cir::ReturnOp>(loc);
}

// This is copied from CodeGenModule::MayDropFunctionReturn. This is a
// candidate for sharing between CIRGen and CodeGen.
static bool mayDropFunctionReturn(const ASTContext &astContext,
QualType returnType) {
// We can't just discard the return value for a record type with a complex
// destructor or a non-trivially copyable type.
if (const RecordType *recordType =
returnType.getCanonicalType()->getAs<RecordType>()) {
if (const auto *classDecl = dyn_cast<CXXRecordDecl>(recordType->getDecl()))
return classDecl->hasTrivialDestructor();
}
return returnType.isTriviallyCopyableType(astContext);
}

void CIRGenFunction::LexicalScope::emitImplicitReturn() {
CIRGenBuilderTy &builder = cgf.getBuilder();
LexicalScope *localScope = cgf.curLexScope;

const auto *fd = cast<clang::FunctionDecl>(cgf.curGD.getDecl());

// In C++, flowing off the end of a non-void function is always undefined
// behavior. In C, flowing off the end of a non-void function is undefined
// behavior only if the non-existent return value is used by the caller.
// That influences whether the terminating op is trap, unreachable, or
// return.
if (cgf.getLangOpts().CPlusPlus && !fd->hasImplicitReturnZero() &&
!cgf.sawAsmBlock && !fd->getReturnType()->isVoidType() &&
builder.getInsertionBlock()) {
bool shouldEmitUnreachable =
cgf.cgm.getCodeGenOpts().StrictReturn ||
!mayDropFunctionReturn(fd->getASTContext(), fd->getReturnType());

if (shouldEmitUnreachable) {
if (cgf.cgm.getCodeGenOpts().OptimizationLevel == 0)
builder.create<cir::TrapOp>(localScope->endLoc);
else
builder.create<cir::UnreachableOp>(localScope->endLoc);
builder.clearInsertionPoint();
return;
}
}

(void)emitReturn(localScope->endLoc);
}

void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
cir::FuncOp fn, cir::FuncType funcType,
FunctionArgList args, SourceLocation loc,
SourceLocation startLoc) {
assert(!curFn &&
"CIRGenFunction can only be used for one function at a time");

fnRetTy = returnType;
curFn = fn;

const auto *fd = dyn_cast_or_null<FunctionDecl>(gd.getDecl());
Expand Down Expand Up @@ -194,6 +312,12 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
builder.CIRBaseBuilderTy::createStore(fnBodyBegin, paramVal, addrVal);
}
assert(builder.getInsertionBlock() && "Should be valid");

// When the current function is not void, create an address to store the
// result value.
if (!returnType->isVoidType())
emitAndUpdateRetAlloca(returnType, getLoc(fd->getBody()->getEndLoc()),
getContext().getTypeAlignInChars(returnType));
}

void CIRGenFunction::finishFunction(SourceLocation endLoc) {}
Expand All @@ -208,9 +332,24 @@ mlir::LogicalResult CIRGenFunction::emitFunctionBody(const clang::Stmt *body) {
return result;
}

static void eraseEmptyAndUnusedBlocks(cir::FuncOp func) {
// Remove any leftover blocks that are unreachable and empty, since they do
// not represent unreachable code useful for warnings nor anything deemed
// useful in general.
SmallVector<mlir::Block *> blocksToDelete;
for (mlir::Block &block : func.getBlocks()) {
if (block.empty() && block.getUses().empty())
blocksToDelete.push_back(&block);
}
for (mlir::Block *block : blocksToDelete)
block->erase();
}

cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
cir::FuncType funcType) {
const auto funcDecl = cast<FunctionDecl>(gd.getDecl());
curGD = gd;

SourceLocation loc = funcDecl->getLocation();
Stmt *body = funcDecl->getBody();
SourceRange bodyRange =
Expand All @@ -219,55 +358,53 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
SourceLocRAIIObject fnLoc{*this, loc.isValid() ? getLoc(loc)
: builder.getUnknownLoc()};

// This will be used once more code is upstreamed.
[[maybe_unused]] mlir::Block *entryBB = fn.addEntryBlock();
auto validMLIRLoc = [&](clang::SourceLocation clangLoc) {
return clangLoc.isValid() ? getLoc(clangLoc) : builder.getUnknownLoc();
};
const mlir::Location fusedLoc = mlir::FusedLoc::get(
&getMLIRContext(),
{validMLIRLoc(bodyRange.getBegin()), validMLIRLoc(bodyRange.getEnd())});
mlir::Block *entryBB = fn.addEntryBlock();

FunctionArgList args;
QualType retTy = buildFunctionArgList(gd, args);

startFunction(gd, retTy, fn, funcType, args, loc, bodyRange.getBegin());

if (isa<CXXDestructorDecl>(funcDecl))
getCIRGenModule().errorNYI(bodyRange, "C++ destructor definition");
else if (isa<CXXConstructorDecl>(funcDecl))
getCIRGenModule().errorNYI(bodyRange, "C++ constructor definition");
else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice &&
funcDecl->hasAttr<CUDAGlobalAttr>())
getCIRGenModule().errorNYI(bodyRange, "CUDA kernel");
else if (isa<CXXMethodDecl>(funcDecl) &&
cast<CXXMethodDecl>(funcDecl)->isLambdaStaticInvoker())
getCIRGenModule().errorNYI(bodyRange, "Lambda static invoker");
else if (funcDecl->isDefaulted() && isa<CXXMethodDecl>(funcDecl) &&
(cast<CXXMethodDecl>(funcDecl)->isCopyAssignmentOperator() ||
cast<CXXMethodDecl>(funcDecl)->isMoveAssignmentOperator()))
getCIRGenModule().errorNYI(bodyRange, "Default assignment operator");
else if (body) {
if (mlir::failed(emitFunctionBody(body))) {
fn.erase();
return nullptr;
}
} else
llvm_unreachable("no definition for normal function");

// This code to insert a cir.return or cir.trap at the end of the function is
// temporary until the function return code, including
// CIRGenFunction::LexicalScope::emitImplicitReturn(), is upstreamed.
mlir::Block &lastBlock = fn.getRegion().back();
if (lastBlock.empty() || !lastBlock.mightHaveTerminator() ||
!lastBlock.getTerminator()->hasTrait<mlir::OpTrait::IsTerminator>()) {
builder.setInsertionPointToEnd(&lastBlock);
if (mlir::isa<cir::VoidType>(funcType.getReturnType())) {
builder.create<cir::ReturnOp>(getLoc(bodyRange.getEnd()));
{
LexicalScope lexScope(*this, fusedLoc, entryBB);

startFunction(gd, retTy, fn, funcType, args, loc, bodyRange.getBegin());

if (isa<CXXDestructorDecl>(funcDecl))
getCIRGenModule().errorNYI(bodyRange, "C++ destructor definition");
else if (isa<CXXConstructorDecl>(funcDecl))
getCIRGenModule().errorNYI(bodyRange, "C++ constructor definition");
else if (getLangOpts().CUDA && !getLangOpts().CUDAIsDevice &&
funcDecl->hasAttr<CUDAGlobalAttr>())
getCIRGenModule().errorNYI(bodyRange, "CUDA kernel");
else if (isa<CXXMethodDecl>(funcDecl) &&
cast<CXXMethodDecl>(funcDecl)->isLambdaStaticInvoker())
getCIRGenModule().errorNYI(bodyRange, "Lambda static invoker");
else if (funcDecl->isDefaulted() && isa<CXXMethodDecl>(funcDecl) &&
(cast<CXXMethodDecl>(funcDecl)->isCopyAssignmentOperator() ||
cast<CXXMethodDecl>(funcDecl)->isMoveAssignmentOperator()))
getCIRGenModule().errorNYI(bodyRange, "Default assignment operator");
else if (body) {
if (mlir::failed(emitFunctionBody(body))) {
fn.erase();
return nullptr;
}
} else {
builder.create<cir::TrapOp>(getLoc(bodyRange.getEnd()));
// Anything without a body should have been handled above.
llvm_unreachable("no definition for normal function");
}
}

if (mlir::failed(fn.verifyBody()))
return nullptr;
if (mlir::failed(fn.verifyBody()))
return nullptr;

finishFunction(bodyRange.getEnd());
finishFunction(bodyRange.getEnd());
}

eraseEmptyAndUnusedBlocks(fn);
return fn;
}

Expand Down
Loading