Skip to content

[CIR] Upstream initial support for switch statements #137106

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 9 commits into from
Apr 29, 2025
2 changes: 2 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRDialect.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ class SameFirstOperandAndResultType

using BuilderCallbackRef =
llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>;
using BuilderOpStateCallbackRef = llvm::function_ref<void(
mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>;

namespace cir {
void buildTerminatedBody(mlir::OpBuilder &builder, mlir::Location loc);
Expand Down
222 changes: 219 additions & 3 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,8 @@ def StoreOp : CIR_Op<"store", [
//===----------------------------------------------------------------------===//

def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "IfOp",
"DoWhileOp", "WhileOp", "ForOp"]>,
"SwitchOp", "DoWhileOp","WhileOp",
"ForOp", "CaseOp"]>,
Terminator]> {
let summary = "Return from function";
let description = [{
Expand Down Expand Up @@ -609,8 +610,9 @@ def ConditionOp : CIR_Op<"condition", [
//===----------------------------------------------------------------------===//

def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
ParentOneOf<["IfOp", "ScopeOp", "WhileOp",
"ForOp", "DoWhileOp"]>]> {
ParentOneOf<["IfOp", "ScopeOp", "SwitchOp",
"WhileOp", "ForOp", "CaseOp",
"DoWhileOp"]>]> {
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 @@ -753,6 +755,220 @@ def ScopeOp : CIR_Op<"scope", [
];
}

//===----------------------------------------------------------------------===//
// SwitchOp
//===----------------------------------------------------------------------===//

def CaseOpKind_DT : I32EnumAttrCase<"Default", 1, "default">;
def CaseOpKind_EQ : I32EnumAttrCase<"Equal", 2, "equal">;
def CaseOpKind_AO : I32EnumAttrCase<"Anyof", 3, "anyof">;
def CaseOpKind_RG : I32EnumAttrCase<"Range", 4, "range">;

def CaseOpKind : I32EnumAttr<
"CaseOpKind",
"case kind",
[CaseOpKind_DT, CaseOpKind_EQ, CaseOpKind_AO, CaseOpKind_RG]> {
let cppNamespace = "::cir";
}

def CaseOp : CIR_Op<"case", [
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
RecursivelySpeculatable, AutomaticAllocationScope]> {
let summary = "Case operation";
let description = [{
The `cir.case` operation represents a case within a C/C++ switch.
The `cir.case` operation must be in a `cir.switch` operation directly
or indirectly.

The `cir.case` have 4 kinds:
- `equal, <constant>`: equality of the second case operand against the
condition.
- `anyof, [constant-list]`: equals to any of the values in a subsequent
following list.
- `range, [lower-bound, upper-bound]`: the condition is within the closed
interval.
- `default`: any other value.

Each case region must be explicitly terminated.
}];

let arguments = (ins ArrayAttr:$value, CaseOpKind:$kind);
let regions = (region AnyRegion:$caseRegion);

let assemblyFormat = "`(` $kind `,` $value `)` $caseRegion attr-dict";

let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins "mlir::ArrayAttr":$value,
"CaseOpKind":$kind,
"mlir::OpBuilder::InsertPoint &":$insertPoint)>
];
}

def SwitchOp : CIR_Op<"switch",
[SameVariadicOperandSize,
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> {
let summary = "Switch operation";
let description = [{
The `cir.switch` operation represents C/C++ switch functionality for
conditionally executing multiple regions of code. The operand to an switch
is an integral condition value.

The set of `cir.case` operations and their enclosing `cir.switch`
represents the semantics of a C/C++ switch statement. Users can use
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
represents the semantics of a C/C++ switch statement. Users can use
represent the semantics of a C/C++ switch statement. Users can use

Sorry I missed this one in my earlier grammar corrections.

`collectCases(llvm::SmallVector<CaseOp> &cases)` to collect the `cir.case`
operation in the `cir.switch` operation easily.

The `cir.case` operations doesn't have to be in the region of `cir.switch`
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
The `cir.case` operations doesn't have to be in the region of `cir.switch`
The `cir.case` operations don't have to be in the region of `cir.switch`

This one too.

directly. However, when all the `cir.case` operations live in the region
of `cir.switch` directly and there are no other operations except the ending
`cir.yield` operation in the region of `cir.switch` directly, we say the
`cir.switch` operation is in a simple form. Users can use
`bool isSimpleForm(llvm::SmallVector<CaseOp> &cases)` member function to
detect if the `cir.switch` operation is in a simple form. The simple form
makes it easier for analyses to handle the `cir.switch` operation
and makes the boundary to give up clear.

To make the simple form as common as possible, CIR code generation attaches
operations corresponding to the statements that lives between top level
cases into the closest `cir.case` operation.

For example,

```
switch(int cond) {
case 4:
a++;
b++;
case 5:
c++;

...
}
```

The statement `b++` is not a sub-statement of the case statement `case 4`.
But to make the generated `cir.switch` a simple form, we will attach the
statement `b++` into the closest `cir.case` operation. So that the generated
code will be like:

```
cir.switch(int cond) {
cir.case(equal, 4) {
a++;
b++;
cir.yield
}
cir.case(equal, 5) {
c++;
cir.yield
}
...
}
```

For the same reason, we will hoist the case statement as the substatement
of another case statement so that they will be in the same level. For
example,

```
switch(int cond) {
case 4:
default;
case 5:
a++;
...
}
```

will be generated as

```
cir.switch(int cond) {
cir.case(equal, 4) {
cir.yield
}
cir.case(default) {
cir.yield
}
cir.case(equal, 5) {
a++;
cir.yield
}
...
}
```

The cir.switch is not be considered "simple" if any of the following is
true:
- There are case statements of the switch statement that are scope
other than the top level compound statement scope. Note that a case
statement itself doesn't form a scope.
- The sub-statement of the switch statement is not a compound statement.
- There is any code before the first case statement. For example,

```
switch(int cond) {
l:
b++;

case 4:
a++;
break;

case 5:
goto l;
...
}
```

the generated CIR for this non-simple switch would be:

```
cir.switch(int cond) {
cir.label "l"
b++;
cir.case(4) {
a++;
cir.break
}
cir.case(5) {
goto "l"
}
cir.yield
}
```
}];

let arguments = (ins CIR_IntType:$condition);

let regions = (region AnyRegion:$body);

let skipDefaultBuilders = 1;
let builders = [
OpBuilder<(ins "mlir::Value":$condition,
"BuilderOpStateCallbackRef":$switchBuilder)>
];

let assemblyFormat = [{
custom<SwitchOp>(
$body, $condition, type($condition)
)
attr-dict
}];

let extraClassDeclaration = [{
// Collect cases in the switch.
void collectCases(llvm::SmallVectorImpl<CaseOp> &cases);

// Check if the switch is in a simple form.
// If yes, collect the cases to \param cases.
// This is an expensive and need to be used with caution.
bool isSimpleForm(llvm::SmallVectorImpl<CaseOp> &cases);
}];
}

//===----------------------------------------------------------------------===//
// BrOp
//===----------------------------------------------------------------------===//
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ struct MissingFeatures {
static bool targetSpecificCXXABI() { return false; }
static bool moduleNameHash() { return false; }
static bool setDSOLocal() { return false; }
static bool foldCaseStmt() { return false; }
static bool constantFoldSwitchStatement() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
18 changes: 18 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class CIRGenFunction : public CIRGenTypeCache {
/// declarations.
DeclMapTy localDeclMap;

/// The type of the condition for the emitting switch statement.
llvm::SmallVector<mlir::Type, 2> condTypeStack;

clang::ASTContext &getContext() const { return cgm.getASTContext(); }

CIRGenBuilderTy &getBuilder() { return builder; }
Expand Down Expand Up @@ -469,6 +472,16 @@ class CIRGenFunction : public CIRGenTypeCache {
ReturnValueSlot returnValue = ReturnValueSlot());
CIRGenCallee emitCallee(const clang::Expr *e);

template <typename T>
mlir::LogicalResult emitCaseDefaultCascade(const T *stmt, mlir::Type condType,
mlir::ArrayAttr value,
cir::CaseOpKind kind,
bool buildingTopLevelCase);

mlir::LogicalResult emitCaseStmt(const clang::CaseStmt &s,
mlir::Type condType,
bool buildingTopLevelCase);

mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);

Expand Down Expand Up @@ -595,6 +608,11 @@ class CIRGenFunction : public CIRGenTypeCache {

mlir::Value emitStoreThroughBitfieldLValue(RValue src, LValue dstresult);

mlir::LogicalResult emitSwitchBody(const clang::Stmt *s);
mlir::LogicalResult emitSwitchCase(const clang::SwitchCase &s,
bool buildingTopLevelCase);
mlir::LogicalResult emitSwitchStmt(const clang::SwitchStmt &s);

/// Given a value and its clang type, returns the value casted to its memory
/// representation.
/// Note: CIR defers most of the special casting to the final lowering passes
Expand Down
Loading