Skip to content

Commit 9d1f1c4

Browse files
[CIR] Upstream initial support for switch statements (#137106)
This introduces initial support for the switchOP, with caseOP of the Equal kind. Support for additional case kinds such as AnyOf, Range, and Default will be included in a future patch. Similarly, lowering to LLVM IR and flattening of the switch are deferred and will be addressed in subsequent updates.
1 parent ea688c0 commit 9d1f1c4

File tree

8 files changed

+1014
-5
lines changed

8 files changed

+1014
-5
lines changed

clang/include/clang/CIR/Dialect/IR/CIRDialect.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ class SameFirstOperandAndResultType
5959

6060
using BuilderCallbackRef =
6161
llvm::function_ref<void(mlir::OpBuilder &, mlir::Location)>;
62+
using BuilderOpStateCallbackRef = llvm::function_ref<void(
63+
mlir::OpBuilder &, mlir::Location, mlir::OperationState &)>;
6264

6365
namespace cir {
6466
void buildTerminatedBody(mlir::OpBuilder &builder, mlir::Location loc);

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

Lines changed: 219 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,8 @@ def StoreOp : CIR_Op<"store", [
470470
//===----------------------------------------------------------------------===//
471471

472472
def ReturnOp : CIR_Op<"return", [ParentOneOf<["FuncOp", "ScopeOp", "IfOp",
473-
"DoWhileOp", "WhileOp", "ForOp"]>,
473+
"SwitchOp", "DoWhileOp","WhileOp",
474+
"ForOp", "CaseOp"]>,
474475
Terminator]> {
475476
let summary = "Return from function";
476477
let description = [{
@@ -609,8 +610,9 @@ def ConditionOp : CIR_Op<"condition", [
609610
//===----------------------------------------------------------------------===//
610611

611612
def YieldOp : CIR_Op<"yield", [ReturnLike, Terminator,
612-
ParentOneOf<["IfOp", "ScopeOp", "WhileOp",
613-
"ForOp", "DoWhileOp"]>]> {
613+
ParentOneOf<["IfOp", "ScopeOp", "SwitchOp",
614+
"WhileOp", "ForOp", "CaseOp",
615+
"DoWhileOp"]>]> {
614616
let summary = "Represents the default branching behaviour of a region";
615617
let description = [{
616618
The `cir.yield` operation terminates regions on different CIR operations,
@@ -753,6 +755,220 @@ def ScopeOp : CIR_Op<"scope", [
753755
];
754756
}
755757

758+
//===----------------------------------------------------------------------===//
759+
// SwitchOp
760+
//===----------------------------------------------------------------------===//
761+
762+
def CaseOpKind_DT : I32EnumAttrCase<"Default", 1, "default">;
763+
def CaseOpKind_EQ : I32EnumAttrCase<"Equal", 2, "equal">;
764+
def CaseOpKind_AO : I32EnumAttrCase<"Anyof", 3, "anyof">;
765+
def CaseOpKind_RG : I32EnumAttrCase<"Range", 4, "range">;
766+
767+
def CaseOpKind : I32EnumAttr<
768+
"CaseOpKind",
769+
"case kind",
770+
[CaseOpKind_DT, CaseOpKind_EQ, CaseOpKind_AO, CaseOpKind_RG]> {
771+
let cppNamespace = "::cir";
772+
}
773+
774+
def CaseOp : CIR_Op<"case", [
775+
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
776+
RecursivelySpeculatable, AutomaticAllocationScope]> {
777+
let summary = "Case operation";
778+
let description = [{
779+
The `cir.case` operation represents a case within a C/C++ switch.
780+
The `cir.case` operation must be in a `cir.switch` operation directly
781+
or indirectly.
782+
783+
The `cir.case` have 4 kinds:
784+
- `equal, <constant>`: equality of the second case operand against the
785+
condition.
786+
- `anyof, [constant-list]`: equals to any of the values in a subsequent
787+
following list.
788+
- `range, [lower-bound, upper-bound]`: the condition is within the closed
789+
interval.
790+
- `default`: any other value.
791+
792+
Each case region must be explicitly terminated.
793+
}];
794+
795+
let arguments = (ins ArrayAttr:$value, CaseOpKind:$kind);
796+
let regions = (region AnyRegion:$caseRegion);
797+
798+
let assemblyFormat = "`(` $kind `,` $value `)` $caseRegion attr-dict";
799+
800+
let skipDefaultBuilders = 1;
801+
let builders = [
802+
OpBuilder<(ins "mlir::ArrayAttr":$value,
803+
"CaseOpKind":$kind,
804+
"mlir::OpBuilder::InsertPoint &":$insertPoint)>
805+
];
806+
}
807+
808+
def SwitchOp : CIR_Op<"switch",
809+
[SameVariadicOperandSize,
810+
DeclareOpInterfaceMethods<RegionBranchOpInterface>,
811+
RecursivelySpeculatable, AutomaticAllocationScope, NoRegionArguments]> {
812+
let summary = "Switch operation";
813+
let description = [{
814+
The `cir.switch` operation represents C/C++ switch functionality for
815+
conditionally executing multiple regions of code. The operand to an switch
816+
is an integral condition value.
817+
818+
The set of `cir.case` operations and their enclosing `cir.switch`
819+
represent the semantics of a C/C++ switch statement. Users can use
820+
`collectCases(llvm::SmallVector<CaseOp> &cases)` to collect the `cir.case`
821+
operation in the `cir.switch` operation easily.
822+
823+
The `cir.case` operations don't have to be in the region of `cir.switch`
824+
directly. However, when all the `cir.case` operations live in the region
825+
of `cir.switch` directly and there are no other operations except the ending
826+
`cir.yield` operation in the region of `cir.switch` directly, we say the
827+
`cir.switch` operation is in a simple form. Users can use
828+
`bool isSimpleForm(llvm::SmallVector<CaseOp> &cases)` member function to
829+
detect if the `cir.switch` operation is in a simple form. The simple form
830+
makes it easier for analyses to handle the `cir.switch` operation
831+
and makes the boundary to give up clear.
832+
833+
To make the simple form as common as possible, CIR code generation attaches
834+
operations corresponding to the statements that lives between top level
835+
cases into the closest `cir.case` operation.
836+
837+
For example,
838+
839+
```
840+
switch(int cond) {
841+
case 4:
842+
a++;
843+
b++;
844+
case 5:
845+
c++;
846+
847+
...
848+
}
849+
```
850+
851+
The statement `b++` is not a sub-statement of the case statement `case 4`.
852+
But to make the generated `cir.switch` a simple form, we will attach the
853+
statement `b++` into the closest `cir.case` operation. So that the generated
854+
code will be like:
855+
856+
```
857+
cir.switch(int cond) {
858+
cir.case(equal, 4) {
859+
a++;
860+
b++;
861+
cir.yield
862+
}
863+
cir.case(equal, 5) {
864+
c++;
865+
cir.yield
866+
}
867+
...
868+
}
869+
```
870+
871+
For the same reason, we will hoist the case statement as the substatement
872+
of another case statement so that they will be in the same level. For
873+
example,
874+
875+
```
876+
switch(int cond) {
877+
case 4:
878+
default;
879+
case 5:
880+
a++;
881+
...
882+
}
883+
```
884+
885+
will be generated as
886+
887+
```
888+
cir.switch(int cond) {
889+
cir.case(equal, 4) {
890+
cir.yield
891+
}
892+
cir.case(default) {
893+
cir.yield
894+
}
895+
cir.case(equal, 5) {
896+
a++;
897+
cir.yield
898+
}
899+
...
900+
}
901+
```
902+
903+
The cir.switch is not be considered "simple" if any of the following is
904+
true:
905+
- There are case statements of the switch statement that are scope
906+
other than the top level compound statement scope. Note that a case
907+
statement itself doesn't form a scope.
908+
- The sub-statement of the switch statement is not a compound statement.
909+
- There is any code before the first case statement. For example,
910+
911+
```
912+
switch(int cond) {
913+
l:
914+
b++;
915+
916+
case 4:
917+
a++;
918+
break;
919+
920+
case 5:
921+
goto l;
922+
...
923+
}
924+
```
925+
926+
the generated CIR for this non-simple switch would be:
927+
928+
```
929+
cir.switch(int cond) {
930+
cir.label "l"
931+
b++;
932+
cir.case(4) {
933+
a++;
934+
cir.break
935+
}
936+
cir.case(5) {
937+
goto "l"
938+
}
939+
cir.yield
940+
}
941+
```
942+
}];
943+
944+
let arguments = (ins CIR_IntType:$condition);
945+
946+
let regions = (region AnyRegion:$body);
947+
948+
let skipDefaultBuilders = 1;
949+
let builders = [
950+
OpBuilder<(ins "mlir::Value":$condition,
951+
"BuilderOpStateCallbackRef":$switchBuilder)>
952+
];
953+
954+
let assemblyFormat = [{
955+
custom<SwitchOp>(
956+
$body, $condition, type($condition)
957+
)
958+
attr-dict
959+
}];
960+
961+
let extraClassDeclaration = [{
962+
// Collect cases in the switch.
963+
void collectCases(llvm::SmallVectorImpl<CaseOp> &cases);
964+
965+
// Check if the switch is in a simple form.
966+
// If yes, collect the cases to \param cases.
967+
// This is an expensive and need to be used with caution.
968+
bool isSimpleForm(llvm::SmallVectorImpl<CaseOp> &cases);
969+
}];
970+
}
971+
756972
//===----------------------------------------------------------------------===//
757973
// BrOp
758974
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,8 @@ struct MissingFeatures {
161161
static bool targetSpecificCXXABI() { return false; }
162162
static bool moduleNameHash() { return false; }
163163
static bool setDSOLocal() { return false; }
164+
static bool foldCaseStmt() { return false; }
165+
static bool constantFoldSwitchStatement() { return false; }
164166

165167
// Missing types
166168
static bool dataMemberType() { return false; }

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ class CIRGenFunction : public CIRGenTypeCache {
6363
/// declarations.
6464
DeclMapTy localDeclMap;
6565

66+
/// The type of the condition for the emitting switch statement.
67+
llvm::SmallVector<mlir::Type, 2> condTypeStack;
68+
6669
clang::ASTContext &getContext() const { return cgm.getASTContext(); }
6770

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

475+
template <typename T>
476+
mlir::LogicalResult emitCaseDefaultCascade(const T *stmt, mlir::Type condType,
477+
mlir::ArrayAttr value,
478+
cir::CaseOpKind kind,
479+
bool buildingTopLevelCase);
480+
481+
mlir::LogicalResult emitCaseStmt(const clang::CaseStmt &s,
482+
mlir::Type condType,
483+
bool buildingTopLevelCase);
484+
472485
mlir::LogicalResult emitContinueStmt(const clang::ContinueStmt &s);
473486
mlir::LogicalResult emitDoStmt(const clang::DoStmt &s);
474487

@@ -595,6 +608,11 @@ class CIRGenFunction : public CIRGenTypeCache {
595608

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

611+
mlir::LogicalResult emitSwitchBody(const clang::Stmt *s);
612+
mlir::LogicalResult emitSwitchCase(const clang::SwitchCase &s,
613+
bool buildingTopLevelCase);
614+
mlir::LogicalResult emitSwitchStmt(const clang::SwitchStmt &s);
615+
598616
/// Given a value and its clang type, returns the value casted to its memory
599617
/// representation.
600618
/// Note: CIR defers most of the special casting to the final lowering passes

0 commit comments

Comments
 (0)