Skip to content

Commit f8bdbed

Browse files
authored
[CIR] Upstream simple function bodies (#127674)
Enable ClangIR generation for very simple functions. The functions have to return `void` or an integral type, contain only compound statements or `return` statements, and `return` statement expressions can only be integral literals of the correct type. The functions can have parameters, but those are currently ignored because there is no way to access them. This change intentionally focuses on breadth (introducing scopes, statements, and expressions) rather than depth, because it enables people to work on upstreaming in parallel without interference. The new ClangIR ops in this change are `ReturnOp`, `YieldOp`, `ScopeOp`, and `TrapOp`. These operations are complete (except for the `ParentOneOf` property) and shouldn't require further upstreaming changes. Significant additions were made to `FuncOp`, adding a type and a region, but that operation is still far from complete. The classes `ScalarExprEmitter` and `CIRGenFunction`, along with the `emit*` functions in `CIRGenFunction` that generate ClangIR for statements, are new in this change. All of these are very incomplete and will be filled out in later upstreaming patches. Existing test `hello.c` is removed and replaced by the new test `func-simple.cpp`. This tests all forms of functions that are currently supported.
1 parent 1fd280d commit f8bdbed

File tree

15 files changed

+1222
-27
lines changed

15 files changed

+1222
-27
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 229 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
///
1212
//===----------------------------------------------------------------------===//
1313

14-
#ifndef LLVM_CLANG_CIR_DIALECT_IR_CIROPS
15-
#define LLVM_CLANG_CIR_DIALECT_IR_CIROPS
14+
#ifndef CLANG_CIR_DIALECT_IR_CIROPS_TD
15+
#define CLANG_CIR_DIALECT_IR_CIROPS_TD
1616

1717
include "clang/CIR/Dialect/IR/CIRDialect.td"
1818
include "clang/CIR/Dialect/IR/CIRTypes.td"
@@ -115,6 +115,165 @@ def ConstantOp : CIR_Op<"const",
115115
let hasFolder = 1;
116116
}
117117

118+
//===----------------------------------------------------------------------===//
119+
// ReturnOp
120+
//===----------------------------------------------------------------------===//
121+
122+
def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp"]>,
123+
Terminator]> {
124+
let summary = "Return from function";
125+
let description = [{
126+
The "return" operation represents a return operation within a function.
127+
The operation takes an optional operand and produces no results.
128+
The operand type must match the signature of the function that contains
129+
the operation.
130+
131+
```mlir
132+
func @foo() -> i32 {
133+
...
134+
cir.return %0 : i32
135+
}
136+
```
137+
}];
138+
139+
// The return operation takes an optional input operand to return. This
140+
// value must match the return type of the enclosing function.
141+
let arguments = (ins Variadic<CIR_AnyType>:$input);
142+
143+
// The return operation only emits the input in the format if it is present.
144+
let assemblyFormat = "($input^ `:` type($input))? attr-dict ";
145+
146+
// Allow building a ReturnOp with no return operand.
147+
let builders = [
148+
OpBuilder<(ins), [{ build($_builder, $_state, std::nullopt); }]>
149+
];
150+
151+
// Provide extra utility definitions on the c++ operation class definition.
152+
let extraClassDeclaration = [{
153+
bool hasOperand() { return getNumOperands() != 0; }
154+
}];
155+
156+
let hasVerifier = 1;
157+
}
158+
159+
//===----------------------------------------------------------------------===//
160+
// YieldOp
161+
//===----------------------------------------------------------------------===//
162+
163+
def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
164+
ParentOneOf<["ScopeOp"]>]> {
165+
let summary = "Represents the default branching behaviour of a region";
166+
let description = [{
167+
The `cir.yield` operation terminates regions on different CIR operations,
168+
and it is used to represent the default branching behaviour of a region.
169+
Said branching behaviour is determinted by the parent operation. For
170+
example, a yield in a `switch-case` region implies a fallthrough, while
171+
a yield in a `cir.if` region implies a branch to the exit block, and so
172+
on.
173+
174+
In some cases, it might yield an SSA value and the semantics of how the
175+
values are yielded is defined by the parent operation. For example, a
176+
`cir.ternary` operation yields a value from one of its regions.
177+
178+
As a general rule, `cir.yield` must be explicitly used whenever a region has
179+
more than one block and no terminator, or within `cir.switch` regions not
180+
`cir.return` terminated.
181+
182+
Examples:
183+
```mlir
184+
cir.if %4 {
185+
...
186+
cir.yield
187+
}
188+
189+
cir.switch (%5) [
190+
case (equal, 3) {
191+
...
192+
cir.yield
193+
}, ...
194+
]
195+
196+
cir.scope {
197+
...
198+
cir.yield
199+
}
200+
201+
%x = cir.scope {
202+
...
203+
cir.yield %val
204+
}
205+
206+
%y = cir.ternary {
207+
...
208+
cir.yield %val : i32
209+
} : i32
210+
```
211+
}];
212+
213+
let arguments = (ins Variadic<CIR_AnyType>:$args);
214+
let assemblyFormat = "($args^ `:` type($args))? attr-dict";
215+
let builders = [
216+
OpBuilder<(ins), [{ /* nothing to do */ }]>,
217+
];
218+
}
219+
220+
//===----------------------------------------------------------------------===//
221+
// ScopeOp
222+
//===----------------------------------------------------------------------===//
223+
224+
def ScopeOp : CIR_Op<"scope", [
225+
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
226+
RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> {
227+
let summary = "Represents a C/C++ scope";
228+
let description = [{
229+
`cir.scope` contains one region and defines a strict "scope" for all new
230+
values produced within its blocks.
231+
232+
The region can contain an arbitrary number of blocks but usually defaults
233+
to one and can optionally return a value (useful for representing values
234+
coming out of C++ full-expressions) via `cir.yield`:
235+
236+
237+
```mlir
238+
%rvalue = cir.scope {
239+
...
240+
cir.yield %value
241+
}
242+
```
243+
244+
The blocks can be terminated by `cir.yield`, `cir.return` or `cir.throw`.
245+
If `cir.scope` yields no value, the `cir.yield` can be left out, and
246+
will be inserted implicitly.
247+
}];
248+
249+
let results = (outs Optional<CIR_AnyType>:$results);
250+
let regions = (region AnyRegion:$scopeRegion);
251+
252+
let hasVerifier = 1;
253+
let skipDefaultBuilders = 1;
254+
let assemblyFormat = [{
255+
custom<OmittedTerminatorRegion>($scopeRegion) (`:` type($results)^)? attr-dict
256+
}];
257+
258+
let extraClassDeclaration = [{
259+
/// Determine whether the scope is empty, meaning it contains a single block
260+
/// terminated by a cir.yield.
261+
bool isEmpty() {
262+
auto &entry = getRegion().front();
263+
return getRegion().hasOneBlock() &&
264+
llvm::isa<YieldOp>(entry.front());
265+
}
266+
}];
267+
268+
let builders = [
269+
// Scopes for yielding values.
270+
OpBuilder<(ins
271+
"llvm::function_ref<void(mlir::OpBuilder &, mlir::Type &, mlir::Location)>":$scopeBuilder)>,
272+
// Scopes without yielding values.
273+
OpBuilder<(ins "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$scopeBuilder)>
274+
];
275+
}
276+
118277
//===----------------------------------------------------------------------===//
119278
// GlobalOp
120279
//===----------------------------------------------------------------------===//
@@ -158,25 +317,86 @@ def GlobalOp : CIR_Op<"global"> {
158317
// FuncOp
159318
//===----------------------------------------------------------------------===//
160319

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

165-
def FuncOp : CIR_Op<"func"> {
323+
def FuncOp : CIR_Op<"func", [
324+
AutomaticAllocationScope, CallableOpInterface, FunctionOpInterface,
325+
IsolatedFromAbove
326+
]> {
166327
let summary = "Declare or define a function";
167328
let description = [{
168329
The `cir.func` operation defines a function, similar to the `mlir::FuncOp`
169330
built-in.
170331
}];
171332

172-
let arguments = (ins SymbolNameAttr:$sym_name);
333+
let arguments = (ins SymbolNameAttr:$sym_name,
334+
TypeAttrOf<CIR_FuncType>:$function_type,
335+
OptionalAttr<DictArrayAttr>:$arg_attrs,
336+
OptionalAttr<DictArrayAttr>:$res_attrs);
337+
338+
let regions = (region AnyRegion:$body);
173339

174340
let skipDefaultBuilders = 1;
175341

176-
let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name)>];
342+
let builders = [OpBuilder<(ins "llvm::StringRef":$sym_name,
343+
"FuncType":$type)>];
344+
345+
let extraClassDeclaration = [{
346+
/// Returns the region on the current operation that is callable. This may
347+
/// return null in the case of an external callable object, e.g. an external
348+
/// function.
349+
::mlir::Region *getCallableRegion();
350+
351+
/// Returns the results types that the callable region produces when
352+
/// executed.
353+
llvm::ArrayRef<mlir::Type> getCallableResults() {
354+
return getFunctionType().getReturnTypes();
355+
}
356+
357+
/// Returns the argument types of this function.
358+
llvm::ArrayRef<mlir::Type> getArgumentTypes() {
359+
return getFunctionType().getInputs();
360+
}
361+
362+
/// Returns 0 or 1 result type of this function (0 in the case of a function
363+
/// returing void)
364+
llvm::ArrayRef<mlir::Type> getResultTypes() {
365+
return getFunctionType().getReturnTypes();
366+
}
367+
368+
/// Hook for OpTrait::FunctionOpInterfaceTrait, called after verifying that
369+
/// the 'type' attribute is present and checks if it holds a function type.
370+
/// Ensures getType, getNumFuncArguments, and getNumFuncResults can be
371+
/// called safely.
372+
llvm::LogicalResult verifyType();
373+
374+
//===------------------------------------------------------------------===//
375+
// SymbolOpInterface Methods
376+
//===------------------------------------------------------------------===//
377+
378+
bool isDeclaration();
379+
}];
177380

178381
let hasCustomAssemblyFormat = 1;
179382
let hasVerifier = 1;
180383
}
181384

182-
#endif // LLVM_CLANG_CIR_DIALECT_IR_CIROPS
385+
//===----------------------------------------------------------------------===//
386+
// TrapOp
387+
//===----------------------------------------------------------------------===//
388+
389+
def TrapOp : CIR_Op<"trap", [Terminator]> {
390+
let summary = "Exit the program abnormally";
391+
let description = [{
392+
The cir.trap operation causes the program to exit abnormally. The
393+
implementations may implement this operation with different mechanisms. For
394+
example, an implementation may implement this operation by calling abort,
395+
while another implementation may implement this operation by executing an
396+
illegal instruction.
397+
}];
398+
399+
let assemblyFormat = "attr-dict";
400+
}
401+
402+
#endif // CLANG_CIR_DIALECT_IR_CIROPS_TD
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef CLANG_CIR_TYPEEVALUATIONKIND_H
10+
#define CLANG_CIR_TYPEEVALUATIONKIND_H
11+
12+
namespace cir {
13+
14+
// This is copied from clang/lib/CodeGen/CodeGenFunction.h. That file (1) is
15+
// not available as an include from ClangIR files, and (2) has lots of stuff
16+
// that we don't want in ClangIR.
17+
enum TypeEvaluationKind { TEK_Scalar, TEK_Complex, TEK_Aggregate };
18+
19+
} // namespace cir
20+
21+
#endif // CLANG_CIR_TYPEEVALUATIONKIND_H
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
//
9+
// Emit Expr nodes with scalar CIR types as CIR code.
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "CIRGenFunction.h"
14+
15+
#include "clang/AST/Expr.h"
16+
#include "clang/AST/StmtVisitor.h"
17+
18+
#include "mlir/IR/Value.h"
19+
20+
#include <cassert>
21+
22+
using namespace clang;
23+
using namespace clang::CIRGen;
24+
25+
namespace {
26+
27+
class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> {
28+
CIRGenFunction &cgf;
29+
CIRGenBuilderTy &builder;
30+
bool ignoreResultAssign;
31+
32+
public:
33+
ScalarExprEmitter(CIRGenFunction &cgf, CIRGenBuilderTy &builder,
34+
bool ira = false)
35+
: cgf(cgf), builder(builder), ignoreResultAssign(ira) {}
36+
37+
//===--------------------------------------------------------------------===//
38+
// Visitor Methods
39+
//===--------------------------------------------------------------------===//
40+
41+
mlir::Value Visit(Expr *e) {
42+
return StmtVisitor<ScalarExprEmitter, mlir::Value>::Visit(e);
43+
}
44+
45+
mlir::Value VisitStmt(Stmt *s) {
46+
llvm_unreachable("Statement passed to ScalarExprEmitter");
47+
}
48+
49+
mlir::Value VisitExpr(Expr *e) {
50+
cgf.getCIRGenModule().errorNYI(
51+
e->getSourceRange(), "scalar expression kind: ", e->getStmtClassName());
52+
return {};
53+
}
54+
55+
mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) {
56+
mlir::Type type = cgf.convertType(e->getType());
57+
return builder.create<cir::ConstantOp>(
58+
cgf.getLoc(e->getExprLoc()), type,
59+
builder.getAttr<cir::IntAttr>(type, e->getValue()));
60+
}
61+
};
62+
} // namespace
63+
64+
/// Emit the computation of the specified expression of scalar type.
65+
mlir::Value CIRGenFunction::emitScalarExpr(const Expr *e) {
66+
assert(e && hasScalarEvaluationKind(e->getType()) &&
67+
"Invalid scalar expression to emit");
68+
69+
return ScalarExprEmitter(*this, builder).Visit(const_cast<Expr *>(e));
70+
}

0 commit comments

Comments
 (0)