[ESI] Support service generators producing service requests

The service request wiring code now picks up connection requests issued
by service generators previously run. This allows hierarchical services.

Too difficult to test with CIRCT tests. Will be tested in the PyCDE
tests in a subsequent PR.
This commit is contained in:
John Demme 2024-10-18 02:13:14 +00:00
parent 625299def8
commit 9fecf63ae7
1 changed files with 49 additions and 31 deletions

View File

@ -404,7 +404,8 @@ struct ESIConnectServicesPass
/// For any service which is "local" (provides the requested service) in a
/// module, replace it with a ServiceImplementOp. Said op is to be replaced
/// with an instantiation by a generator.
LogicalResult replaceInst(ServiceInstanceOp, Block *portReqs);
LogicalResult replaceInst(ServiceInstanceOp,
ArrayRef<ServiceImplementConnReqOp> portReqs);
/// Figure out which requests are "local" vs need to be surfaced. Call
/// 'surfaceReqs' and/or 'replaceInst' as appropriate.
@ -475,36 +476,50 @@ LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
Block &modBlock = mod->getRegion(0).front();
// The non-local reqs which need to be surfaced from this module.
SmallVector<ServiceImplementConnReqOp, 4> nonLocalReqs;
SetVector<ServiceImplementConnReqOp> nonLocalReqs;
// Index the local services and create blocks in which to put the requests.
DenseMap<SymbolRefAttr, Block *> localImplReqs;
Block *anyServiceInst = nullptr;
for (auto instOp : modBlock.getOps<ServiceInstanceOp>()) {
auto *b = new Block();
localImplReqs[instOp.getServiceSymbolAttr()] = b;
if (!instOp.getServiceSymbolAttr())
anyServiceInst = b;
}
llvm::MapVector<SymbolRefAttr, llvm::SetVector<ServiceImplementConnReqOp>>
localImplReqs;
for (auto instOp : modBlock.getOps<ServiceInstanceOp>())
localImplReqs[instOp.getServiceSymbolAttr()] = {};
// AFTER we assemble the local services table (and it will not change the
// location of the values), get the pointer to the default service instance,
// if any.
llvm::SetVector<ServiceImplementConnReqOp> *anyServiceInst = nullptr;
if (auto defaultService = localImplReqs.find(SymbolRefAttr());
defaultService != localImplReqs.end())
anyServiceInst = &defaultService->second;
// Sort the various requests by destination.
mod.walk([&](ServiceImplementConnReqOp req) {
auto service = req.getServicePort().getModuleRef();
auto implOpF = localImplReqs.find(service);
if (implOpF != localImplReqs.end())
req->moveBefore(implOpF->second, implOpF->second->end());
else if (anyServiceInst)
req->moveBefore(anyServiceInst, anyServiceInst->end());
else
nonLocalReqs.push_back(req);
});
auto sortConnReqs = [&]() {
// Sort the various requests by destination.
for (auto req : llvm::make_early_inc_range(
mod.getBodyBlock()->getOps<ServiceImplementConnReqOp>())) {
auto service = req.getServicePort().getModuleRef();
auto reqListIter = localImplReqs.find(service);
if (reqListIter != localImplReqs.end())
reqListIter->second.insert(req);
else if (anyServiceInst)
anyServiceInst->insert(req);
else
nonLocalReqs.insert(req);
}
};
// Bootstrap the sorting.
sortConnReqs();
// Replace each service instance with a generation request. If a service
// generator is registered, generate the server.
for (auto instOp :
llvm::make_early_inc_range(modBlock.getOps<ServiceInstanceOp>())) {
Block *portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
if (failed(replaceInst(instOp, portReqs)))
auto portReqs = localImplReqs[instOp.getServiceSymbolAttr()];
if (failed(replaceInst(instOp, portReqs.getArrayRef())))
return failure();
// Find any new requests which were created by a generator.
for (RequestConnectionOp req : llvm::make_early_inc_range(
mod.getBodyBlock()->getOps<RequestConnectionOp>()))
convertReq(req);
sortConnReqs();
}
// Surface all of the requests which cannot be fulfilled locally.
@ -512,14 +527,13 @@ LogicalResult ESIConnectServicesPass::process(hw::HWModuleLike mod) {
return success();
if (auto mutableMod = dyn_cast<hw::HWMutableModuleLike>(mod.getOperation()))
return surfaceReqs(mutableMod, nonLocalReqs);
return surfaceReqs(mutableMod, nonLocalReqs.getArrayRef());
return mod.emitOpError(
"Cannot surface requests through module without mutable ports");
}
LogicalResult ESIConnectServicesPass::replaceInst(ServiceInstanceOp instOp,
Block *portReqs) {
assert(portReqs);
LogicalResult ESIConnectServicesPass::replaceInst(
ServiceInstanceOp instOp, ArrayRef<ServiceImplementConnReqOp> portReqs) {
auto declSym = instOp.getServiceSymbolAttr();
ServiceDeclOpInterface decl;
if (declSym) {
@ -534,7 +548,7 @@ LogicalResult ESIConnectServicesPass::replaceInst(ServiceInstanceOp instOp,
// + the to_client types.
SmallVector<Type, 8> resultTypes(instOp.getResultTypes().begin(),
instOp.getResultTypes().end());
for (auto req : portReqs->getOps<ServiceImplementConnReqOp>())
for (auto req : portReqs)
resultTypes.push_back(req.getBundleType());
// Create the generation request op.
@ -544,18 +558,22 @@ LogicalResult ESIConnectServicesPass::replaceInst(ServiceInstanceOp instOp,
instOp.getServiceSymbolAttr(), instOp.getImplTypeAttr(),
getStdService(declSym), instOp.getImplOptsAttr(), instOp.getOperands());
implOp->setDialectAttrs(instOp->getDialectAttrs());
implOp.getPortReqs().push_back(portReqs);
Block &reqBlock = implOp.getPortReqs().emplaceBlock();
// Update the users.
for (auto [n, o] : llvm::zip(implOp.getResults(), instOp.getResults()))
o.replaceAllUsesWith(n);
unsigned instOpNumResults = instOp.getNumResults();
for (auto [idx, req] :
llvm::enumerate(portReqs->getOps<ServiceImplementConnReqOp>())) {
for (size_t idx = 0, e = portReqs.size(); idx < e; ++idx) {
ServiceImplementConnReqOp req = portReqs[idx];
req.getToClient().replaceAllUsesWith(
implOp.getResult(idx + instOpNumResults));
}
for (auto req : portReqs)
req->moveBefore(&reqBlock, reqBlock.end());
implOp->dump();
// Erase the instance first in case it consumes any channels or bundles. If it
// does, the service generator will fail to verify the IR as there will be
// multiple uses.