<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/61301>61301</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
[MLIR][Tablegen] Tablegen generated pass registration functions don't correctly pass in
</td>
</tr>
<tr>
<th>Labels</th>
<td>
new issue
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
ataheridezfouli-groq
</td>
</tr>
</table>
<pre>
I've been trying to refactor the passes in my project to use tablegen definitions. I have noticed that passes which require options to be passed in require a lot more boilerplate to be written, which could be automatically generated by tablegen.
As a case study, I looked at the Async dialect. For example they define a pass in tablegen with three options:
```
def AsyncParallelFor : Pass<"async-parallel-for", "ModuleOp"> {
...
let constructor = "mlir::createAsyncParallelForPass()";
let options = [
Option<"asyncDispatch", "async-dispatch",
"bool", /*default=*/"true",
"Dispatch async compute tasks using recursive work splitting. If `false` "
"async compute tasks will be launched using simple for loop in the "
"caller thread.">,
Option<"numWorkerThreads", "num-workers",
"int32_t", /*default=*/"8",
"The number of available workers to execute async operations. If `-1` "
"the value will be retrieved from the runtime.">,
Option<"minTaskSize", "min-task-size",
"int32_t", /*default=*/"1000",
"The minimum task size for sharding parallel operation.">
];
...
}
```
The autogenerated code base class and registration function look like this:
```
template <typename DerivedT>
class AsyncParallelForBase : public ::mlir::OperationPass<ModuleOp> {
public:
using Base = AsyncParallelForBase;
AsyncParallelForBase() : ::mlir::OperationPass<ModuleOp>(::mlir::TypeID::get<DerivedT>()) {}
AsyncParallelForBase(const AsyncParallelForBase &other) : ::mlir::OperationPass<ModuleOp>(other) {}
...
AsyncParallelForBase(const AsyncParallelForOptions &options) : AsyncParallelForBase() {
asyncDispatch = options.asyncDispatch;
numWorkerThreads = options.numWorkerThreads;
minTaskSize = options.minTaskSize;
}
protected:
::mlir::Pass::Option<bool> asyncDispatch{*this, "async-dispatch", ::llvm::cl::desc("Dispatch async compute tasks using recursive work splitting. If `false` async compute tasks will be launched using simple for loop in the caller thread."), ::llvm::cl::init(true)};
::mlir::Pass::Option<int32_t> numWorkerThreads{*this, "num-workers", ::llvm::cl::desc("The number of available workers to execute async operations. If `-1` the value will be retrieved from the runtime."), ::llvm::cl::init(8)};
::mlir::Pass::Option<int32_t> minTaskSize{*this, "min-task-size", ::llvm::cl::desc("The minimum task size for sharding parallel operation."), ::llvm::cl::init(1000)};
private:
};
inline void registerAsyncParallelFor() {
::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> {
return mlir::createAsyncParallelForPass();
});
}
```
Here the base class does not provide a constructor to set the arguments directly (e.g. bool, int32_t, int32_t), which falls to the inherited pass class to implement. In addition, the auto generated `registerAsyncParallelFor` is calling the `createAsyncParallelForPass` function with no arguments. Modifying the `let constructor` in tablegen to pass in options does not propagate up to the register function. In addition, it cannot differentiate between a function definition vs a function call, e.g. being able to drop the argument types in the function call.
In order to make this work for both registration and addition to the pass manager, a hand written constructor must be created for the pass, in addition to two separate `createAsyncParallelForPass` functions must be defined:
```
struct AsyncParallelForPass
: public impl::AsyncParallelForBase<AsyncParallelForPass> {
AsyncParallelForPass() = default;
AsyncParallelForPass(bool asyncDispatch, int32_t numWorkerThreads,
int32_t minTaskSize) {
this->asyncDispatch = asyncDispatch;
this->numWorkerThreads = numWorkerThreads;
this->minTaskSize = minTaskSize;
}
...
};
std::unique_ptr<Pass> mlir::createAsyncParallelForPass() {
return std::make_unique<AsyncParallelForPass>();
}
std::unique_ptr<Pass> mlir::createAsyncParallelForPass(bool asyncDispatch,
int32_t numWorkerThreads,
int32_t minTaskSize) {
return std::make_unique<AsyncParallelForPass>(asyncDispatch, numWorkerThreads,
minTaskSize);
}
```
This boilerplate could be avoided by some modifications to the tablegen parser. For example, tablegen should be able to do the following:
1. Generate a second constructor in the pass base class that takes in the arguments directly and sets them. As a result the derived pass class would not need to define any constructors (similar to the no options case).
2. Allow the `let constructor = createPassName` to accept arguments. Propagate these arguments properly to the register function. As a result only a single `createPassName` function should be declared by the user.
Thanks very much for your help! 😄
</pre>
<img width="1px" height="1px" alt="" src="http://email.email.llvm.org/o/eJysWVtv2zoS_jXMyyCCLDW-PPghjZvdANttsRtgHwtaHFncUKRKUvbx-fUHQ919SdKcBoXjiuJw5uPwm28Y7pzcacQ1u_vM7jY3vPaFsWvueYFWCvwzN7WStztrft5sjTiun1iy2CNsETV4e5R6B96AxZxn3ljwBULFnUMHUkN5hMqa_2Pm6aXaIXi-VbhDDQJzqaWXRrsInqDgewRtvMxQgC-476wcCpkVYPFnLS2CqcIMsrZtFxK0UDfOQRkPpbEIWyMV2kpxj-3rByu9R82Sh9ZqZmolaITX3pTcy4wrdYQdarTco4DtsXc4YvGGxffN570DDhl3CM7X4kgWn0AZ84ICuA8g3LujzkBIrjDzETwaC_gHLyuFNHxs4ieHKQgKoUfmIH0BvrDYh8vSdl02j9t_4b8C82ad79xypVDRKiy9h-_cOZY-sCThNHxbteO3ubEsSchfliRfjagVfqvoSfoF2OJzYxYgiqLuq0IPmdHO2zpsMEs3NLdU0pJb6X1mkXs8dSN4kCxZsgrWP4_Ra4x2WxkM3vVLA3wLAyP3N9JV3GfF4HkTlZg8HwwAvbI1RvUTHllCYPFaeZZuWHIfHiXe1nhxcrcihIUgM2VVUxpx9-KgdpT0FrPaOrlHOBj7Aq5S0nupdxE85cDmcc6VQzaPydyJ9UtGD1IpykTFa50VKNpVnAwZkxtL6VWFPCnwgk3KXLQhbbiImh3t47qMra7L_xn7gvY5THIDvLoubw9hyF2ER2qfJj_8W_AuL05-LhB0XW7RgsmB77lUlPnQLkhnFf_AjKBpgDIVncaWKAK2t7PLwBI0e65q7OG06K3EPQrIrSkDdrbWXpb4PoxKqZ-5e_mv_BMHeEqpb2nXbl3_-GMAzeI4vopRKbUs6zLkB9BKIQtcwa2gzOiO9ABPF1Frit1t-nM3OtFssbnIJc0nLUxkOFBgZgTClqguU8RUXAuwuJPON6tCXussfCH-AyVfiODkVc7yWDaczNIHf6xQ8xJhg1buUTz37jdrnZLKZ3KD-K2qt0pm0BDQQEXfOiha_usJbsxuzdzePWhPWmt7c3HRMwK7-FKgu-DfLzhGs07efj5W-LRpvu_Qs_RhjE9LqqsQUbeZVz0K1H0FyGRufIH2Y04PU0dunNePD7n3rSsNybyrgK2Pr-G-GNWQSd0I29oaiqYVJR3NOeXDybQzshzPHLHEZNKYPYb3e7QqazxmHsUoGU83ocG-2Y6WlkJlS79MY6Tok_tw8K6XyNa6Uvuyrd2q-S3QZQHG31r5_n6dO69qlPrX4yBFyZJlU9dXBPQI9reR7Wg7_XKWDGf4ntXId4D720rfLxe6d8G2_JuYjdP9FK4LNfOdgH2sDr4r4Kb-TmKurNxzj0P1Go01n1IrEu57I7tCiPaUls4p6RTJbmank0P31U67JTCdF82btZY_a_xReUuS5OKGfJmSn0VfWw2_INGn5DR68Lpa-Cfa0M2M9YEw6KiRo8ZvLwV1OOP2wRtw2HRI3O7qErV3IKTFzKsjsGSJ0S6CRrw_QK-jRl9XQ_-Wc6XCqSFzUlPHSpIldFSNN95AYBZaJ4InDVyI0HSSEd-KnVHDx-bx1T2dxyBdoKTQ9JIMn8evQDuPB20Uejpthpgj-GqEzI8jUyedVlhv1BZ607eKXes0xrriO1JVddXh0cXRO3EWv_SQcU3zhcxztKi9JBtb9Adq7_ng_9Ctw96NBwgOstVsG1I4gdK8AWFNNdloIL3nOm6fmJh0108ajBUYkqXkrZ5sig0d_a3xxVSCkibtAuvCD1iVXPMdyZQH4FDQa-0dwCQny9p54tBmL0VYpDPRpN7U-oFymHjH_0IKuH6ZpvMX1wRy49aZ0gn2-iM-EsGU380xv6xcHy5amnLGdWoIeqZvXd5Qwe00Or0n6mQ4wOeVddr9nP108yat2IncowQh1jyXfdflXjfnoux7Ve51M09l3xty76QJm4LZ7f0l0u827P2EPkanrQa9aTpRPxr715PjpC6cCPzf4ubFLHk1E976-WiGvdfu9Qz8KMZnh-S3uT719c16_kwMO74xHa5GSeg0V6HOlAgllS2Z8f4elpiyL1IVtw7t5LozVNpu3BW93a5KNBZyo5Q5SL0baDF8ziL4R1udgYPDzGgxIe-2mgS6H8mQcIXs-ctQby7IDSoIDj29jWUE4VbXoqtVI1BE03WP9cQhuE8lUyOK4H97j6uPY7eod106WUrFbQeSNn3hzkLbumrZIIngnqK_IgQCtzRHiTLn37wM3ZU3wLMMKz8WFd97IeALdOOgSSKgVcdX5ME4fqMJH6DmTI2q3NiBvoAPmyowU9y2F-cFQk3JML1f4vrFwR7tEcqaJJyxcDS1hQJVxZIZsMeYrR7ZasmWn-BGrFOxSlf8Btez-WKZLpaLu9VNsU4xn6XzOJ2ld-mnxWqVb2Pky3wulmmczLZ4I9dJnKRxGq9m89lqtopm2zhNVzlfLZZ8vkqRfYqx5FJF1B1Exu5upHM1ruezNJ7dKL5F5cIfRZJE4wHCIDUWd5sbu6Y5t9t659inWEnn3WDFS6_CX1O-_uvpP6Tp7z4_t9nP7jbQfR9pzpBeF6_USOFpliwoHWybs50GvKmtWhfeV6EhSx5Z8riTvqi3UWZKljyGpqf5ddv-DYYljyEMx5LHEOZfAQAA__9DljQV">