Skip to content

[CIR] Upstream simple function bodies #127674

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 20, 2025
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
238 changes: 229 additions & 9 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_CIR_DIALECT_IR_CIROPS
#define LLVM_CLANG_CIR_DIALECT_IR_CIROPS
#ifndef CLANG_CIR_DIALECT_IR_CIROPS_TD
#define CLANG_CIR_DIALECT_IR_CIROPS_TD

include "clang/CIR/Dialect/IR/CIRDialect.td"
include "clang/CIR/Dialect/IR/CIRTypes.td"
Expand Down Expand Up @@ -115,6 +115,165 @@ def ConstantOp : CIR_Op<"const",
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// ReturnOp
//===----------------------------------------------------------------------===//

def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp"]>,
Terminator]> {
let summary = "Return from function";
let description = [{
The "return" operation represents a return operation within a function.
The operation takes an optional operand and produces no results.
The operand type must match the signature of the function that contains
the operation.

```mlir
func @foo() -> i32 {
...
cir.return %0 : i32
}
```
}];

// The return operation takes an optional input operand to return. This
// value must match the return type of the enclosing function.
let arguments = (ins Variadic<CIR_AnyType>:$input);

// The return operation only emits the input in the format if it is present.
let assemblyFormat = "($input^ `:` type($input))? attr-dict ";

// Allow building a ReturnOp with no return operand.
let builders = [
OpBuilder<(ins), [{ build($_builder, $_state, std::nullopt); }]>
];

// Provide extra utility definitions on the c++ operation class definition.
let extraClassDeclaration = [{
bool hasOperand() { return getNumOperands() != 0; }
}];

let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// YieldOp
//===----------------------------------------------------------------------===//

def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
ParentOneOf<["ScopeOp"]>]> {
let summary = "Represents the default branching behaviour of a region";
let description = [{
The `cir.yield` operation terminates regions on different CIR operations,
and it is used to represent the default branching behaviour of a region.
Said branching behaviour is determinted by the parent operation. For
example, a yield in a `switch-case` region implies a fallthrough, while
a yield in a `cir.if` region implies a branch to the exit block, and so
on.

In some cases, it might yield an SSA value and the semantics of how the
values are yielded is defined by the parent operation. For example, a
`cir.ternary` operation yields a value from one of its regions.

As a general rule, `cir.yield` must be explicitly used whenever a region has
more than one block and no terminator, or within `cir.switch` regions not
`cir.return` terminated.

Examples:
```mlir
cir.if %4 {
...
cir.yield
}

cir.switch (%5) [
case (equal, 3) {
...
cir.yield
}, ...
]

cir.scope {
...
cir.yield
}

%x = cir.scope {
...
cir.yield %val
}

%y = cir.ternary {
...
cir.yield %val : i32
} : i32
```
}];

let arguments = (ins Variadic<CIR_AnyType>:$args);
let assemblyFormat = "($args^ `:` type($args))? attr-dict";
let builders = [
OpBuilder<(ins), [{ /* nothing to do */ }]>,
];
}

//===----------------------------------------------------------------------===//
// ScopeOp
//===----------------------------------------------------------------------===//

def ScopeOp : CIR_Op<"scope", [
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> {
let summary = "Represents a C/C++ scope";
let description = [{
`cir.scope` contains one region and defines a strict "scope" for all new
values produced within its blocks.

The region can contain an arbitrary number of blocks but usually defaults
to one and can optionally return a value (useful for representing values
coming out of C++ full-expressions) via `cir.yield`:


```mlir
%rvalue = cir.scope {
...
cir.yield %value
}
```

The blocks can be terminated by `cir.yield`, `cir.return` or `cir.throw`.
If `cir.scope` yields no value, the `cir.yield` can be left out, and
will be inserted implicitly.
}];

let results = (outs Optional<CIR_AnyType>:$results);
let regions = (region AnyRegion:$scopeRegion);

let hasVerifier = 1;
let skipDefaultBuilders = 1;
let assemblyFormat = [{
custom<OmittedTerminatorRegion>($scopeRegion) (`:` type($results)^)? attr-dict
}];

let extraClassDeclaration = [{
/// Determine whether the scope is empty, meaning it contains a single block
/// terminated by a cir.yield.
bool isEmpty() {
auto &entry = getRegion().front();
return getRegion().hasOneBlock() &&
llvm::isa<YieldOp>(entry.front());
}
}];

let builders = [
// Scopes for yielding values.
OpBuilder<(ins
"llvm::function_ref<void(mlir::OpBuilder &, mlir::Type &, mlir::Location)>":$scopeBuilder)>,
// Scopes without yielding values.
OpBuilder<(ins "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$scopeBuilder)>
];
}

//===----------------------------------------------------------------------===//
// GlobalOp
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -158,25 +317,86 @@ def GlobalOp : CIR_Op<"global"> {
// FuncOp
//===----------------------------------------------------------------------===//

// TODO(CIR): For starters, cir.func has only name, nothing else. The other
// properties of a function will be added over time as more of ClangIR is
// upstreamed.
// TODO(CIR): FuncOp is still a tiny shell of what it will become. Many more
// properties and attributes will be added as upstreaming continues.

def FuncOp : CIR_Op<"func"> {
def FuncOp : CIR_Op<"func", [
AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface,
IsolatedFromAbove
]> {
let summary = "Declare or define a function";
let description = [{
The `cir.func` operation defines a function, similar to the `mlir::FuncOp`
built-in.
}];

let arguments = (ins SymbolNameAttr:$sym_name);
let arguments = (ins SymbolNameAttr:$sym_name,
TypeAttrOf<CIR_FuncType>:$function_type,
OptionalAttr<DictArrayAttr>:$arg_attrs,
OptionalAttr<DictArrayAttr>:$res_attrs);

let regions = (region AnyRegion:$body);

let skipDefaultBuilders = 1;

let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name)>];
let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name,
"FuncType":$type)>];

let extraClassDeclaration = [{
/// Returns the region on the current operation that is callable. This may
/// return null in the case of an external callable object, e.g. an external
/// function.
::mlir::Region *getCallableRegion();

/// Returns the results types that the callable region produces when
/// executed.
llvm::ArrayRef<mlir::Type> getCallableResults() {
return getFunctionType().getReturnTypes();
}

/// Returns the argument types of this function.
llvm::ArrayRef<mlir::Type> getArgumentTypes() {
return getFunctionType().getInputs();
}

/// Returns 0 or 1 result type of this function (0 in the case of a function
/// returing void)
llvm::ArrayRef<mlir::Type> getResultTypes() {
return getFunctionType().getReturnTypes();
}

/// Hook for OpTrait::FunctionOpInterfaceTrait, called after verifying that
/// the 'type' attribute is present and checks if it holds a function type.
/// Ensures getType, getNumFuncArguments, and getNumFuncResults can be
/// called safely.
llvm::LogicalResult verifyType();

//===------------------------------------------------------------------===//
// SymbolOpInterface Methods
//===------------------------------------------------------------------===//

bool isDeclaration();
}];

let hasCustomAssemblyFormat = 1;
let hasVerifier = 1;
}

#endif // LLVM_CLANG_CIR_DIALECT_IR_CIROPS
//===----------------------------------------------------------------------===//
// TrapOp
//===----------------------------------------------------------------------===//

def TrapOp : CIR_Op<"trap", [Terminator]> {
let summary = "Exit the program abnormally";
let description = [{
The cir.trap operation causes the program to exit abnormally. The
implementations may implement this operation with different mechanisms. For
example, an implementation may implement this operation by calling abort,
while another implementation may implement this operation by executing an
illegal instruction.
}];

let assemblyFormat = "attr-dict";
}

#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD
21 changes: 21 additions & 0 deletions clang/include/clang/CIR/TypeEvaluationKind.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef CLANG_CIR_TYPEEVALUATIONKIND_H
#define CLANG_CIR_TYPEEVALUATIONKIND_H

namespace cir {

// This is copied from clang/lib/CodeGen/CodeGenFunction.h. That file (1) is
// not available as an include from ClangIR files, and (2) has lots of stuff
// that we don't want in ClangIR.
enum TypeEvaluationKind { TEK_Scalar, TEK_Complex, TEK_Aggregate };

} // namespace cir

#endif // CLANG_CIR_TYPEEVALUATIONKIND_H
70 changes: 70 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Emit Expr nodes with scalar CIR types as CIR code.
//
//===----------------------------------------------------------------------===//

#include "CIRGenFunction.h"

#include "clang/AST/Expr.h"
#include "clang/AST/StmtVisitor.h"

#include "mlir/IR/Value.h"

#include <cassert>

using namespace clang;
using namespace clang::CIRGen;

namespace {

class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
CIRGenFunction &cgf;
CIRGenBuilderTy &builder;
bool ignoreResultAssign;

public:
ScalarExprEmitter(CIRGenFunction &cgf, CIRGenBuilderTy &builder,
bool ira = false)
: cgf(cgf), builder(builder), ignoreResultAssign(ira) {}

//===--------------------------------------------------------------------===//
// Visitor Methods
//===--------------------------------------------------------------------===//

mlir::Value Visit(Expr *e) {
return StmtVisitor<ScalarExprEmitter, mlir::Value>::Visit(e);
}

mlir::Value VisitStmt(Stmt *s) {
llvm_unreachable("Statement passed to ScalarExprEmitter");
}

mlir::Value VisitExpr(Expr *e) {
cgf.getCIRGenModule().errorNYI(
e->getSourceRange(), "scalar expression kind: ", e->getStmtClassName());
return {};
}

mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) {
mlir::Type type = cgf.convertType(e->getType());
return builder.create<cir::ConstantOp>(
cgf.getLoc(e->getExprLoc()), type,
builder.getAttr<cir::IntAttr>(type, e->getValue()));
}
};
} // namespace

/// Emit the computation of the specified expression of scalar type.
mlir::Value CIRGenFunction::emitScalarExpr(const Expr *e) {
assert(e && hasScalarEvaluationKind(e->getType()) &&
"Invalid scalar expression to emit");

return ScalarExprEmitter(*this, builder).Visit(const_cast<Expr *>(e));
}
Loading