Add switch elimination pass for BMv2 target support Transforms switch…#298
Add switch elimination pass for BMv2 target support Transforms switch…#298devalgupta404 wants to merge 7 commits intop4lang:mainfrom
Conversation
|
@PietroGhg please review it |
|
Hi @devalgupta404 thanks for this PR, I haven't looked in detail at the code yet, just from looking at the output of the test you added I think that this is missing the creation of a temporary variable to which we assign the condition expression of the switch statement (it's done here in the original p4c pass). I think this is necessary to do otherwise the whole apply block can't be correctly expressed in the final json representation. Please add that and then I'll take a better look at the code, thanks :) |
6c83110 to
399da9b
Compare
lib/Transforms/SwitchElimination.cpp
Outdated
|
|
||
| namespace { | ||
|
|
||
| class NameGenerator { |
There was a problem hiding this comment.
Could you use SymbolTable::generateSymbolName for this, e.g. translate.cpp
lib/Transforms/SwitchElimination.cpp
Outdated
| void runOnOperation() override; | ||
|
|
||
| private: | ||
| LogicalResult eliminateSwitch(P4HIR::ControlOp control, P4HIR::SwitchOp switchOp, |
There was a problem hiding this comment.
nit: eliminateSwitch and isActionRunSwitch can be made static
lib/Transforms/SwitchElimination.cpp
Outdated
| { | ||
| OpBuilder::InsertionGuard guard(tableBuilder); | ||
| tableBuilder.setInsertionPointToStart(&tableKeyOp.getBody().front()); | ||
| auto keyArg = tableKeyOp.getBody().front().addArgument(condType, tableLoc); |
There was a problem hiding this comment.
I think the argument here is already present and you should just use getArgument(0)
mtsamis
left a comment
There was a problem hiding this comment.
Hi devalgupta404, thanks for the PR!
I've made some comments for an issue with isActionRunSwitch plus some others for the code structure, please have a look.
| bool isActionRunSwitch(P4HIR::SwitchOp switchOp); | ||
| }; | ||
|
|
||
| bool SwitchEliminationPass::isActionRunSwitch(P4HIR::SwitchOp switchOp) { |
There was a problem hiding this comment.
This doesn't look enough to classify a switch as an action_run one. You're looking if the type of the condition is a struct which has an action_run field which has two issues:
- You don't know if
action_runis actually used in that particular switch, only that it exists in general. - You cannot differentiate from a user-defined type that has an unrelated field named
action_run(since it isn't really a unique name).
I think you need to inspect the condition value rather than the type. This way you should be able to address these issues by e.g. following it's definitions and make sure it's coming from a table_apply, plus that action_run is the actual field used there.
lib/Transforms/SwitchElimination.cpp
Outdated
| } | ||
|
|
||
| llvm::SmallVector<std::string> actionNames; | ||
| llvm::SmallVector<mlir::Attribute> enumFields; |
There was a problem hiding this comment.
It looks from below that enumFields is always builder.getStringAttr(actionNames), so not necessary to keep two vectors, just one.
Could probably be a single vector of mlir::StringAttr (so you can get the string as needed). If it's only used in FlatSymbolRefAttr you can probably just pass the StringAttr there directly anyway.
lib/Transforms/SwitchElimination.cpp
Outdated
| tableBuilder.create<P4HIR::TableEntriesOp>( | ||
| tableLoc, true, DictionaryAttr(), | ||
| [&](OpBuilder &entriesBuilder, Location entriesLoc) { | ||
| for (size_t i = 0; i < cases.size(); ++i) { |
There was a problem hiding this comment.
Can you please replace all loops that have manual iteration with range-based ones? If you need the index you can use llvm::enumerate, but e.g. here it's not really needed, it's better to do a loop with llvm::zip(cases, actionsNames)
lib/Transforms/SwitchElimination.cpp
Outdated
| for (auto valueAttr : caseOp.getValue()) { | ||
| auto tupleType = TupleType::get(ctx, {condType}); | ||
| auto keyAttr = P4HIR::AggAttr::get( | ||
| tupleType, builder.getArrayAttr({valueAttr})); |
There was a problem hiding this comment.
Better avoid mixing builder and entriesBuilder here (and in some other places).
Also maybe you'd want to consider naming the lambda builder arguments something sort like just b (consistent with other places in the codebase, so I think it's fine). It may help to reduce line length for readability.
lib/Transforms/SwitchElimination.cpp
Outdated
| // Create a temporary variable for the switch key (following p4c's pattern) | ||
| std::string keyName = prefix + "_key"; | ||
| auto keyVar = builder.create<P4HIR::VariableOp>( | ||
| loc, P4HIR::ReferenceType::get(ctx, condType), keyName, hiddenAttr); |
There was a problem hiding this comment.
ctx in P4HIR::ReferenceType::get(ctx, condType) is redundant, you can use P4HIR::ReferenceType::get(condType) (same for some other places).
lib/Transforms/SwitchElimination.cpp
Outdated
| if (!mlir::isa<P4HIR::YieldOp>(&op)) | ||
| caseBuilder.clone(op, mapper); | ||
| } | ||
| caseBuilder.create<P4HIR::YieldOp>(caseLoc); |
lib/Transforms/SwitchElimination.cpp
Outdated
| auto hiddenAttr = builder.getDictionaryAttr( | ||
| {builder.getNamedAttr("hidden", builder.getUnitAttr())}); | ||
|
|
||
| for (size_t i = 0; i < cases.size(); ++i) { |
There was a problem hiding this comment.
There is some fair amount of duplication with the handling of normal / default cases. E.g. here the body which is not small is duplicated below with only the name changing. You can consider using a lambda with the code outside (e.g. createActionForCase(llvm::StringRef name), to avoid the duplication.
I'm not sure, but it may also be possible to eliminate the cases vector, and iterate over cases with an if for the default case and avoid the duplication altogether.
lib/Transforms/SwitchElimination.cpp
Outdated
| }); | ||
| } | ||
|
|
||
| if (defaultCase) { |
There was a problem hiding this comment.
Same as at the top for default case duplication.
lib/Transforms/SwitchElimination.cpp
Outdated
| auto module = getOperation(); | ||
| NameGenerator nameGen; | ||
|
|
||
| llvm::SmallVector<std::pair<P4HIR::ControlOp, P4HIR::SwitchOp>> toProcess; |
There was a problem hiding this comment.
It looks like we don't need a vector here, we can just call eliminateSwitch while iterating in a single loop and e.g. use WalkResult::interrupt if it fails. Also isActionRunSwitch is also checked at the top of eliminateSwitch so there's no need to double call it, you can pick one place that you like.
|
|
||
| // CHECK-LABEL: p4hir.control @c | ||
| module { | ||
| p4hir.control @c(%arg0: !p4hir.ref<!b32i> {p4hir.dir = #p4hir<dir inout>, p4hir.param_name = "b"})() { |
There was a problem hiding this comment.
The CHECK directives here look insufficient. Can you please match the IR that we expect to be generated from this pass? Looking at your pass I believe we would at least need to match the newly introduced p4hir.table's contents and the resulting SwitchOp / actions?
There was a problem hiding this comment.
Second that. We also need to ensure that whole logic is covered including handling of action_run switches.
You'd better take the existing p4c testcases and port them if you're out of ideas :)
b197d48 to
197d619
Compare
|
@mtsamis @PietroGhg I’ve made the changes as suggested above. Could you please review it? |
197d619 to
47ea203
Compare
|
Hi @devalgupta404 changes look good on my side, let's wait for @mtsamis to take a look too |
|
@mtsamis please take a look on updated changes |
|
Hi @devalgupta404, looking at the latest version of your code I can say it still needs more polishing; In general, things I have mentioned in my previous detailed review aren't fully addressed (code duplication, elimination of cases vector, good tests with full IR matching, use of builders and their names) or were applied hastily (e.g. comment about yield copying was partially addressed, llvm::enumerate is overused when there are better alternatives). I kindly ask you to have a look at these again and try to improve the implementation based on the ideas rather than just applying my suggestions 🙂 Since I did re-review the implementation, I can also provide some further feedback to help you refining this. On the high level: Some parts of code are currently not readable due to being terse, nested and not appropriately commented (Your table and switch op building). These currently contain three nested lambdas, with the inner ones being quite hard to read. You don't necessarily have to nest everything together in these lambdas, you can probably just build the outer op and then add things after the fact inside it. I will also add some inline comments, for more specific things. |
lib/Transforms/SwitchElimination.cpp
Outdated
| [&](llvm::StringRef candidate) { | ||
| return symbolTable.lookup(candidate) != nullptr; | ||
| }, | ||
| counter).str().str(); |
There was a problem hiding this comment.
.str().str(); looks like code smell, probably can be refactored to something better (you can see examples of generateSymbolName in mlir repo maybe).
lib/Transforms/SwitchElimination.cpp
Outdated
| actionNames.push_back(actionName); | ||
| enumFields.push_back(builder.getStringAttr(actionName)); | ||
|
|
||
| auto createAction = [&](mlir::StringAttr actionName) { |
There was a problem hiding this comment.
There's no reason to keep actionNames.push_back or creation of stringattr outside this lambda, that's why I mentioned a llvm::StringRef version in my review.
lib/Transforms/SwitchElimination.cpp
Outdated
| }, | ||
| counter).str().str(); | ||
|
|
||
| llvm::SmallVector<P4HIR::CaseOp> cases; |
There was a problem hiding this comment.
I still believe that we shouldn't have this vector and refactor the rest of the code appropriately.
lib/Transforms/SwitchElimination.cpp
Outdated
| auto funcOp = P4HIR::FuncOp::buildAction(builder, loc, actionName, funcType, | ||
| {}, hiddenAttr); | ||
| {}, hiddenAttr); | ||
| OpBuilder funcBuilder(funcOp.getBody()); |
There was a problem hiding this comment.
Let's avoid creating OpBuilders, maybe just an InsertionGuard would do here?
lib/Transforms/SwitchElimination.cpp
Outdated
|
|
||
| auto loc = switchOp.getLoc(); | ||
| auto condType = switchOp.getCondition().getType(); | ||
| OpBuilder builder(switchOp); |
There was a problem hiding this comment.
You're currently using an OpBuilder to create ops and direct calls for things like erase. It would be better to create an IRRewriter instead of OpBuilder and do everything from there.
lib/Transforms/SwitchElimination.cpp
Outdated
| defaultBuilder.create<P4HIR::CallOp>(defaultLoc, controlRef, ValueRange{}); | ||
| }); | ||
|
|
||
| tableBuilder.create<P4HIR::TableEntriesOp>( |
There was a problem hiding this comment.
See high level comment for all this code
| control->walk([&](P4HIR::SwitchOp switchOp) { | ||
| if (!isActionRunSwitch(switchOp)) | ||
| toProcess.emplace_back(control, switchOp); | ||
| return control->walk([&](P4HIR::SwitchOp switchOp) { |
There was a problem hiding this comment.
I think we would need to propagate the error and do something like signalPassFailure if there's one using wasInterrupted? Also maybe we should consider using an OpRewritePattern if applicable (probably on control op)?
| // CHECK: p4hir.table_apply @c::@_switch_0_table with key(%[[KEY_VAL]]) | ||
| // CHECK: p4hir.struct_extract | ||
| // CHECK: p4hir.switch | ||
| // CHECK: %{{.*}} = p4hir.variable ["_switch{{.*}}_key"] annotations {hidden} |
There was a problem hiding this comment.
These tests are still very insufficient for what the pass is doing. Please have a look at other tests from optimization passes that do IR transformations (one example could be test/Transforms/Passes/InlineControls/inline-3.mlir)
lib/Transforms/SwitchElimination.cpp
Outdated
| extractOp.getInput().getDefiningOp<P4HIR::TableApplyOp>(); | ||
| if (!tableApply) | ||
| return false; | ||
| return true; |
There was a problem hiding this comment.
Let's avoid unnecessarily verbose code here.
Maybe just if (!extractOp || extractOp.getFieldName() != "action_run") ...
And return extractOp.getInput().getDefiningOp<P4HIR::TableApplyOp>() != nullptr or similar
|
Also, it looks like a variable is introduced that is written and then subsequently read. This doesn't do something in p4mlir and should probably be deleted; it will be canonicalized away anyway. |
I asked for that change (the variable is needed otherwise we can't represent this "pattern" in bmv2), but I didn't think about the canonicalizer removing it. I'm ok with removing it in this pass, we'll handle things in some BMv2-specific pass (sorry for the back and forth @devalgupta404) |
b4e69be to
1ae6f5f
Compare
|
@mtsamis i applied the requested changes please take a look. |
Thanks! Please give me a bit more time to review this 🙂 |
|
Hi @devalgupta404, I went over the current implementation again. The implementation looks better now, but I see that a number of the requested changes are not implemented. Can you revisit and either implement the changes or discuss why you believe they should not be implemented? Thanks! |
Transforms switch statements on arbitrary expressions to table-based switches on action_run. Creates a temporary variable to store the switch condition expression before table apply, following p4c's pattern. Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
Structured bindings cannot be captured in lambdas in C++17 (only C++20+). Changed llvm::zip loops to llvm::enumerate and extract actionName separately to avoid capturing structured bindings inside nested lambdas. Fixes CI build error on Ubuntu 22.04 with clang 14. Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
1ae6f5f to
8de2ceb
Compare
…th OpBuilder::InsertionGuard on the local builder Signed-off-by: devalgupta404 <devalgupta4@gmail.com>
8de2ceb to
d826d66
Compare
|
Hi @mtsamis regarding llvm::zip, switchOp.cases(), pre collecting non default cases in the SmallVector just to enable zip adds unnecessary complexity so instead i use the single loop with conditional logic for default case and there are few changes i missed earlier so now i made all lambda builders renamed to b and replaced rewriter mixing with OpBuilder::InsertionGuard and also extract cloneCase helper with single loop over all cases. |
mtsamis
left a comment
There was a problem hiding this comment.
Hi @mtsamis regarding llvm::zip, switchOp.cases(), pre collecting non default cases in the SmallVector just to enable zip adds unnecessary complexity so instead i use the single loop with conditional logic for default case and there are few changes i missed earlier so now i made all lambda builders renamed to b and replaced rewriter mixing with OpBuilder::InsertionGuard and also extract cloneCase helper with single loop over all cases.
Right, no problem with not using zip if not a good fit.
I was mostly referring to few other things. The most important things that this still needs:
- More readable code: As I mentioned already, the triply-nested builder lambdas make this quite hard to read. These don't really have to be nested as far as I see, so can you refactor that please?
- The tests need some improvement as well,
{{.*}}is used in places where named matches should be used instead.
| [&](OpBuilder &b, Location caseLoc) { | ||
| IRMapping mapper; | ||
| for (auto &op : origCase.getCaseRegion().front()) | ||
| b.clone(op, mapper); |
There was a problem hiding this comment.
Can this something like inlineBlockBefore/inlineRegionBefore be used here instead?
Add switch elimination pass for BMv2 target support
Closes #252
Parent issue: #247
Summary
Transforms switch statements on arbitrary expressions to table-based switches on
action_run. Required for hardware targets (like BMv2) that don't support arbitrary expression switches.Creates a temporary variable to store the switch condition expression before table apply, following p4c's pattern.
Reference
Based on p4c's midend implementation:
Transformation
Input (switch on
bit<32>):Output (table + switch on
action_run):Test Evidence
Full pass output (click to expand)
Files Changed
lib/Transforms/SwitchElimination.cppinclude/p4mlir/Transforms/Passes.tdinclude/p4mlir/Transforms/Passes.hlib/Transforms/CMakeLists.txttest/Transforms/Passes/switch-elimination.mlirHow to Test