Skip to content

[CIR] Upstream initial for-loop support #132266

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 2 commits into from
Mar 21, 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
19 changes: 19 additions & 0 deletions clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,15 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return cir::BoolAttr::get(getContext(), getBoolTy(), state);
}

/// Create a for operation.
cir::ForOp createFor(
mlir::Location loc,
llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)> condBuilder,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this intentionally missing the init? ForStmt has an Init, Condition, Step, and Body, but this handles only 3.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an excellent question. The incubator (and this PR) emits the initialization in front of the for op, but it does seem like it is something we would want more tightly associated with the for in the emitted CIR.

Copy link
Member

@bcardosolopes bcardosolopes Mar 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initialization happens within the scope preceding the actual cir.for, etc. We have no intend of reproducing the AST 100% here, because of nested regions within the loop. the alloca needs to be visible and dominate everything inside cir.for.

llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)> bodyBuilder,
llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)> stepBuilder) {
return create<cir::ForOp>(loc, condBuilder, bodyBuilder, stepBuilder);
}

mlir::TypedAttr getConstPtrAttr(mlir::Type type, int64_t value) {
auto valueAttr = mlir::IntegerAttr::get(
mlir::IntegerType::get(type.getContext(), 64), value);
Expand Down Expand Up @@ -158,6 +167,16 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
return mlir::IntegerAttr::get(mlir::IntegerType::get(ctx, 64),
size.getQuantity());
}

/// Create a loop condition.
cir::ConditionOp createCondition(mlir::Value condition) {
return create<cir::ConditionOp>(condition.getLoc(), condition);
}

/// Create a yield operation.
cir::YieldOp createYield(mlir::Location loc, mlir::ValueRange value = {}) {
return create<cir::YieldOp>(loc, value);
}
};

} // namespace cir
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "clang/CIR/Dialect/IR/CIRAttrs.h"
#include "clang/CIR/Dialect/IR/CIROpsDialect.h.inc"
#include "clang/CIR/Dialect/IR/CIROpsEnums.h"
#include "clang/CIR/Interfaces/CIRLoopOpInterface.h"
#include "clang/CIR/Interfaces/CIROpInterfaces.h"

// TableGen'erated files for MLIR dialects require that a macro be defined when
Expand Down
164 changes: 162 additions & 2 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ include "clang/CIR/Dialect/IR/CIRTypes.td"
include "clang/CIR/Dialect/IR/CIRAttrs.td"

include "clang/CIR/Interfaces/CIROpInterfaces.td"
include "clang/CIR/Interfaces/CIRLoopOpInterface.td"

include "mlir/IR/BuiltinAttributeInterfaces.td"
include "mlir/IR/EnumAttr.td"
Expand Down Expand Up @@ -423,7 +424,7 @@ def StoreOp : CIR_Op<"store", [
// ReturnOp
//===----------------------------------------------------------------------===//

def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp"]>,
def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "ForOp"]>,
Terminator]> {
let summary = "Return from function";
let description = [{
Expand Down Expand Up @@ -460,12 +461,57 @@ def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp"]>,
let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// ConditionOp
//===----------------------------------------------------------------------===//

def ConditionOp : CIR_Op<"condition", [
Terminator,
DeclareOpInterfaceMethods<RegionBranchTerminatorOpInterface,
["getSuccessorRegions"]>
]> {
let summary = "Loop continuation condition.";
let description = [{
The `cir.condition` terminates conditional regions. It takes a single
`cir.bool` operand and, depending on its value, may branch to different
regions:

- When in the `cond` region of a `cir.loop`, it continues the loop
if true, or exits it if false.
- When in the `ready` region of a `cir.await`, it branches to the `resume`
region when true, and to the `suspend` region when false.

Example:

```mlir
cir.loop for(cond : {
cir.condition(%arg0) // Branches to `step` region or exits.
}, step : {
[...]
}) {
[...]
}

cir.await(user, ready : {
cir.condition(%arg0) // Branches to `resume` or `suspend` region.
}, suspend : {
[...]
}, resume : {
[...]
},)
```
}];
let arguments = (ins CIR_BoolType:$condition);
let assemblyFormat = " `(` $condition `)` attr-dict ";
let hasVerifier = 1;
}

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

def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
ParentOneOf<["ScopeOp"]>]> {
ParentOneOf<["ScopeOp", "ForOp"]>]> {
let summary = "Represents the default branching behaviour of a region";
let description = [{
The `cir.yield` operation terminates regions on different CIR operations,
Expand Down Expand Up @@ -666,6 +712,120 @@ def UnaryOp : CIR_Op<"unary", [Pure, SameOperandsAndResultType]> {
let hasFolder = 1;
}

//===----------------------------------------------------------------------===//
// BrCondOp
//===----------------------------------------------------------------------===//

def BrCondOp : CIR_Op<"brcond",
[DeclareOpInterfaceMethods<BranchOpInterface, ["getSuccessorForOperands"]>,
Pure, Terminator, AttrSizedOperandSegments]> {
let summary = "Conditional branch";
let description = [{
The `cir.brcond %cond, ^bb0, ^bb1` branches to 'bb0' block in case
%cond (which must be a !cir.bool type) evaluates to true, otherwise
it branches to 'bb1'.

Example:

```mlir
...
cir.brcond %a, ^bb3, ^bb4
^bb3:
cir.return
^bb4:
cir.yield
```
}];

let builders = [
OpBuilder<(ins "mlir::Value":$cond, "mlir::Block *":$destTrue, "mlir::Block *":$destFalse,
CArg<"mlir::ValueRange", "{}">:$destOperandsTrue,
CArg<"mlir::ValueRange", "{}">:$destOperandsFalse), [{
build($_builder, $_state, cond, destOperandsTrue,
destOperandsFalse, destTrue, destFalse);
}]>
];

let arguments = (ins CIR_BoolType:$cond,
Variadic<CIR_AnyType>:$destOperandsTrue,
Variadic<CIR_AnyType>:$destOperandsFalse);
let successors = (successor AnySuccessor:$destTrue, AnySuccessor:$destFalse);
let assemblyFormat = [{
$cond
$destTrue (`(` $destOperandsTrue^ `:` type($destOperandsTrue) `)`)?
`,`
$destFalse (`(` $destOperandsFalse^ `:` type($destOperandsFalse) `)`)?
attr-dict
}];
}

//===----------------------------------------------------------------------===//
// ForOp
//===----------------------------------------------------------------------===//

def ForOp : CIR_Op<"for", [LoopOpInterface, NoRegionArguments]> {
let summary = "C/C++ for loop counterpart";
let description = [{
Represents a C/C++ for loop. It consists of three regions:

- `cond`: single block region with the loop's condition. Should be
terminated with a `cir.condition` operation.
- `body`: contains the loop body and an arbitrary number of blocks.
- `step`: single block region with the loop's step.

Example:

```mlir
cir.for cond {
cir.condition(%val)
} body {
cir.break
^bb2:
cir.yield
} step {
cir.yield
}
```
}];

let regions = (region SizedRegion<1>:$cond,
MinSizedRegion<1>:$body,
SizedRegion<1>:$step);
let assemblyFormat = [{
`:` `cond` $cond
`body` $body
`step` $step
attr-dict
}];

let builders = [
OpBuilder<(ins "llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$condBuilder,
"llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$bodyBuilder,
"llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>":$stepBuilder), [{
mlir::OpBuilder::InsertionGuard guard($_builder);

// Build condition region.
$_builder.createBlock($_state.addRegion());
condBuilder($_builder, $_state.location);

// Build body region.
$_builder.createBlock($_state.addRegion());
bodyBuilder($_builder, $_state.location);

// Build step region.
$_builder.createBlock($_state.addRegion());
stepBuilder($_builder, $_state.location);
}]>
];

let extraClassDeclaration = [{
mlir::Region *maybeGetStep() { return &getStep(); }
llvm::SmallVector<mlir::Region *> getRegionsInExecutionOrder() {
return llvm::SmallVector<mlir::Region *, 3>{&getCond(), &getBody(), &getStep()};
}
}];
}

//===----------------------------------------------------------------------===//
// GlobalOp
//===----------------------------------------------------------------------===//
Expand Down
34 changes: 34 additions & 0 deletions clang/include/clang/CIR/Interfaces/CIRLoopOpInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===---------------------------------------------------------------------===//
//
// Defines the interface to generically handle CIR loop operations.
//
//===----------------------------------------------------------------------===//

#ifndef CLANG_CIR_INTERFACES_CIRLOOPOPINTERFACE_H
#define CLANG_CIR_INTERFACES_CIRLOOPOPINTERFACE_H

#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/Operation.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/LoopLikeInterface.h"

namespace cir {
namespace detail {

/// Verify invariants of the LoopOpInterface.
mlir::LogicalResult verifyLoopOpInterface(::mlir::Operation *op);

} // namespace detail
} // namespace cir

/// Include the tablegen'd interface declarations.
#include "clang/CIR/Interfaces/CIRLoopOpInterface.h.inc"

#endif // CLANG_CIR_INTERFACES_CIRLOOPOPINTERFACE_H
99 changes: 99 additions & 0 deletions clang/include/clang/CIR/Interfaces/CIRLoopOpInterface.td
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//===---------------------------------------------------------------------===//
//
// 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_INTERFACES_CIRLOOPOPINTERFACE
#define CLANG_CIR_INTERFACES_CIRLOOPOPINTERFACE

include "mlir/IR/OpBase.td"
include "mlir/Interfaces/ControlFlowInterfaces.td"
include "mlir/Interfaces/LoopLikeInterface.td"

def LoopOpInterface : OpInterface<"LoopOpInterface", [
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
DeclareOpInterfaceMethods<LoopLikeOpInterface>
]> {
let description = [{
Contains helper functions to query properties and perform transformations
on a loop.
}];
let cppNamespace = "::cir";

let methods = [
InterfaceMethod<[{
Returns the loop's conditional region.
}],
/*retTy=*/"mlir::Region &",
/*methodName=*/"getCond"
>,
InterfaceMethod<[{
Returns the loop's body region.
}],
/*retTy=*/"mlir::Region &",
/*methodName=*/"getBody"
>,
InterfaceMethod<[{
Returns a pointer to the loop's step region or nullptr.
}],
/*retTy=*/"mlir::Region *",
/*methodName=*/"maybeGetStep",
/*args=*/(ins),
/*methodBody=*/"",
/*defaultImplementation=*/"return nullptr;"
>,
InterfaceMethod<[{
Returns the first region to be executed in the loop.
}],
/*retTy=*/"mlir::Region &",
/*methodName=*/"getEntry",
/*args=*/(ins),
/*methodBody=*/"",
/*defaultImplementation=*/"return $_op.getCond();"
>,
InterfaceMethod<[{
Returns a list of regions in order of execution.
}],
/*retTy=*/"llvm::SmallVector<mlir::Region *>",
/*methodName=*/"getRegionsInExecutionOrder",
/*args=*/(ins),
/*methodBody=*/"",
/*defaultImplementation=*/[{
return llvm::SmallVector<mlir::Region *, 2>{&$_op.getRegion(0), &$_op.getRegion(1)};
}]
>,
InterfaceMethod<[{
Recursively walks the body of the loop in pre-order while skipping
nested loops and executing a callback on every other operation.
}],
/*retTy=*/"mlir::WalkResult",
/*methodName=*/"walkBodySkippingNestedLoops",
/*args=*/(ins "::llvm::function_ref<mlir::WalkResult (mlir::Operation *)>":$callback),
/*methodBody=*/"",
/*defaultImplementation=*/[{
return $_op.getBody().template walk<mlir::WalkOrder::PreOrder>([&](mlir::Operation *op) {
if (mlir::isa<LoopOpInterface>(op))
return mlir::WalkResult::skip();
return callback(op);
});
}]
>
];

let extraClassDeclaration = [{
/// Generic method to retrieve the successors of a LoopOpInterface operation.
static void getLoopOpSuccessorRegions(
::cir::LoopOpInterface op, ::mlir::RegionBranchPoint point,
::mlir::SmallVectorImpl<::mlir::RegionSuccessor> &regions);
}];

let verify = [{
/// Verify invariants of the LoopOpInterface.
return detail::verifyLoopOpInterface($_op);
}];
}

#endif // CLANG_CIR_INTERFACES_CIRLOOPOPINTERFACE
1 change: 1 addition & 0 deletions clang/include/clang/CIR/Interfaces/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ function(add_clang_mlir_type_interface interface)
endfunction()

add_clang_mlir_op_interface(CIROpInterfaces)
add_clang_mlir_op_interface(CIRLoopOpInterface)
add_clang_mlir_type_interface(CIRFPTypeInterface)
Loading