diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index 3972db7f4144..2c05c725d106 100644 --- a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp +++ b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp @@ -46,15 +46,38 @@ struct ESIBuildManifestPass llvm::json::Value json(Operation *errorOp, Attribute, bool elideType = false); // Output a node in the appid hierarchy. - void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp); - // Output the manifest data of a node in the appid hierarchy. - void emitBlock(llvm::json::OStream &, Block &block, StringRef manifestClass); + void emitNode(llvm::json::OStream &, AppIDHierNodeOp nodeOp, + ArrayRef parentPath); + // Output the manifest data of a node in the appid hierarchy. 'nodePath' is + // the absolute appID path of the node which owns 'block'. + void emitBlock(llvm::json::OStream &, Block &block, StringRef manifestClass, + ArrayRef nodePath); AppIDHierRootOp appidRoot; /// Get a JSON representation of the manifest. std::string json(); + /// Walk the appID hierarchy to populate 'servedPortPaths' (absolute appID + /// paths of user-accessible client ports, i.e. those appearing in a service + /// implementation's client record) and 'instantiatedModules' (every module + /// instantiated in the unpruned hierarchy, whose metadata must be retained). + void collectAccessibilityInfo(Block &block); + + /// Walk the appID hierarchy and populate 'keepNodes' with the hierarchy nodes + /// which contain (or have a descendant which contains) a user-accessible + /// client port. + void computeKeepNodes(Block &block); + + /// Is the client port with appID 'portID' under the node at 'nodePath' user + /// accessible? + bool isPortAccessible(ArrayRef nodePath, Attribute portID) const { + SmallVector portPath(nodePath.begin(), nodePath.end()); + portPath.push_back(portID); + return servedPortPaths.contains( + ArrayAttr::get(portID.getContext(), portPath)); + } + // Type table. std::string useType(Type type) { std::string typeID; @@ -73,6 +96,18 @@ struct ESIBuildManifestPass DenseSet symbols; DenseSet modules; + // Modules instantiated anywhere in the (full, unpruned) AppID design + // hierarchy. Their metadata is always retained so that 'module_infos' stays + // complete even when an instance's subtree is pruned from the design tree. + DenseSet instantiatedModules; + + // Absolute appID paths of user-accessible client ports (those served by a + // service or engine via a client record). + DenseSet servedPortPaths; + // AppID hierarchy nodes to retain (those whose subtree contains a + // user-accessible client port). + DenseSet keepNodes; + hw::HWSymbolCache symCache; }; } // anonymous namespace @@ -90,6 +125,11 @@ void ESIBuildManifestPass::runOnOperation() { if (!appidRoot) return; + // Determine which client ports are user-accessible and which hierarchy nodes + // need to be retained as a result. + collectAccessibilityInfo(appidRoot.getChildren().front()); + computeKeepNodes(appidRoot.getChildren().front()); + // JSONify the manifest. std::string jsonManifest = json(); @@ -131,8 +171,87 @@ void ESIBuildManifestPass::runOnOperation() { } } +void ESIBuildManifestPass::collectAccessibilityInfo(Block &rootBlock) { + // Record every module instantiated in the (full, unpruned) AppID design + // hierarchy so that its metadata (module_infos) is always retained. + rootBlock.getParentOp()->walk([&](AppIDHierNodeOp node) { + instantiatedModules.insert(node.getModuleRefAttr()); + }); + + // Iterative pre-order walk over the appID hierarchy to collect served port + // paths. Each work item pairs a block with the absolute appID path of the + // node which owns it. + SmallVector>> worklist; + worklist.emplace_back(&rootBlock, SmallVector{}); + while (!worklist.empty()) { + auto [block, path] = worklist.pop_back_val(); + for (auto svc : block->getOps()) + for (auto client : + svc.getReqDetails().front().getOps()) { + // Any client port with a service implementation record is served by + // that service/engine and is therefore user-accessible. Note that the + // channel assignments may be empty (e.g. for MMIO ports, which are + // described by 'offset'/'size' options rather than channel + // assignments). + SmallVector portPath(path.begin(), path.end()); + for (auto id : client.getRelAppIDPath().getAsRange()) + portPath.push_back(id); + servedPortPaths.insert(ArrayAttr::get(&getContext(), portPath)); + } + for (auto node : block->getOps()) { + SmallVector childPath(path.begin(), path.end()); + childPath.push_back(node.getAppIDAttr()); + worklist.emplace_back(&node.getChildren().front(), std::move(childPath)); + } + } +} + +void ESIBuildManifestPass::computeKeepNodes(Block &rootBlock) { + // A node is kept if its child block has a user-accessible request port or any + // descendant node is kept. Compute this bottom-up without recursion: first + // collect every block in pre-order (so each block precedes its descendants), + // then process the list in reverse (so every descendant is visited before its + // ancestor). + struct WorkItem { + Block *block; + SmallVector path; + AppIDHierNodeOp owner; // Node owning 'block' (null for the root block). + }; + SmallVector worklist; + SmallVector order; + worklist.push_back({&rootBlock, {}, {}}); + while (!worklist.empty()) { + WorkItem item = worklist.pop_back_val(); + for (auto node : item.block->getOps()) { + SmallVector childPath(item.path.begin(), item.path.end()); + childPath.push_back(node.getAppIDAttr()); + worklist.push_back( + {&node.getChildren().front(), std::move(childPath), node}); + } + order.push_back(std::move(item)); + } + + // Maps a block to whether it (or a descendant) is to be kept. + DenseMap blockKeeps; + for (WorkItem &item : llvm::reverse(order)) { + bool keep = false; + for (auto req : item.block->getOps()) + if (isPortAccessible(item.path, req.getRequestorAttr())) + keep = true; + for (auto node : item.block->getOps()) + if (blockKeeps.lookup(&node.getChildren().front())) + keep = true; + blockKeeps[item.block] = keep; + if (keep && item.owner) + keepNodes.insert(item.owner); + } +} + void ESIBuildManifestPass::emitNode(llvm::json::OStream &j, - AppIDHierNodeOp nodeOp) { + AppIDHierNodeOp nodeOp, + ArrayRef parentPath) { + SmallVector nodePath(parentPath.begin(), parentPath.end()); + nodePath.push_back(nodeOp.getAppIDAttr()); std::set classesToEmit; for (auto manifestData : nodeOp.getOps()) classesToEmit.insert(manifestData.getManifestClass()); @@ -141,20 +260,30 @@ void ESIBuildManifestPass::emitNode(llvm::json::OStream &j, j.attribute("instanceOf", json(nodeOp, nodeOp.getModuleRefAttr())); for (StringRef manifestClass : classesToEmit) j.attributeArray(manifestClass.str() + "s", [&]() { - emitBlock(j, nodeOp.getChildren().front(), manifestClass); + emitBlock(j, nodeOp.getChildren().front(), manifestClass, nodePath); }); j.attributeArray("children", [&]() { - for (auto nodeOp : nodeOp.getChildren().front().getOps()) - emitNode(j, nodeOp); + for (auto childOp : + nodeOp.getChildren().front().getOps()) + if (keepNodes.contains(childOp)) + emitNode(j, childOp, nodePath); }); }); } void ESIBuildManifestPass::emitBlock(llvm::json::OStream &j, Block &block, - StringRef manifestClass) { + StringRef manifestClass, + ArrayRef nodePath) { for (auto manifestData : block.getOps()) { if (manifestData.getManifestClass() != manifestClass) continue; + // Prune client ports which are not user-accessible (i.e. whose channels are + // not bound to a host-reachable engine). Such ports never appear in the + // runtime's Accelerator design tree, so they (and their types) are omitted. + if (auto req = + dyn_cast(manifestData.getOperation())) + if (!isPortAccessible(nodePath, req.getRequestorAttr())) + continue; j.object([&] { SmallVector attrs; manifestData.getDetails(attrs); @@ -183,12 +312,13 @@ std::string ESIBuildManifestPass::json() { modules.insert(appidRoot.getTopModuleRefAttr()); for (StringRef manifestClass : classesToEmit) j.attributeArray(manifestClass.str() + "s", [&]() { - emitBlock(j, appidRoot.getChildren().front(), manifestClass); + emitBlock(j, appidRoot.getChildren().front(), manifestClass, {}); }); j.attributeArray("children", [&]() { for (auto nodeOp : appidRoot.getChildren().front().getOps()) - emitNode(j, nodeOp); + if (keepNodes.contains(nodeOp)) + emitNode(j, nodeOp, {}); }); }); @@ -208,7 +338,12 @@ std::string ESIBuildManifestPass::json() { for (auto port : ports) { j.object([&] { j.attribute("name", port.port.getName().getValue()); - j.attribute("typeID", useType(port.type)); + // Emit the port type as a plain string rather than registering it + // in the type table: the runtime never resolves service + // declaration port types, so they would only bloat the manifest. + std::string typeID; + llvm::raw_string_ostream(typeID) << port.type; + j.attribute("typeID", typeID); }); } }); @@ -227,7 +362,16 @@ std::string ESIBuildManifestPass::json() { // Gather all manifest data for each symbol. for (auto symInfo : mod.getBody()->getOps()) { FlatSymbolRefAttr sym = symInfo.getSymbolRefAttr(); - if (!sym || !symbols.contains(sym)) + if (!sym) + continue; + // Keep a module's metadata if it is instantiated anywhere in the design + // hierarchy ('instantiatedModules', collected from the full unpruned + // hierarchy) or is otherwise referenced ('symbols'). Additionally, always + // keep module constants: they are semantically significant (e.g. for + // codegen) even when the module itself is not user-accessible. + bool isConstants = symInfo.getManifestClass() == "symConsts"; + if (!symbols.contains(sym) && !instantiatedModules.contains(sym) && + !isConstants) continue; symbolInfoLookup[sym].push_back(symInfo); } diff --git a/test/Dialect/ESI/manifest.mlir b/test/Dialect/ESI/manifest.mlir index cecc93c7f04a..b07c621aed91 100644 --- a/test/Dialect/ESI/manifest.mlir +++ b/test/Dialect/ESI/manifest.mlir @@ -157,18 +157,7 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-LABEL: "design": { // CHECK-NEXT: "instanceOf": "@top", -// CHECK-NEXT: "clientPorts": [ -// CHECK-NEXT: { -// CHECK-NEXT: "appID": { -// CHECK-NEXT: "name": "func1" -// CHECK-NEXT: }, -// CHECK-NEXT: "typeID": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", -// CHECK-NEXT: "servicePort": { -// CHECK-NEXT: "port": "call", -// CHECK-NEXT: "serviceName": "@funcs" -// CHECK-NEXT: } -// CHECK-NEXT: } -// CHECK-NEXT: ], +// CHECK-NEXT: "clientPorts": [], // CHECK-LABEL: "engines": [ // CHECK-NEXT: { @@ -507,16 +496,6 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: ] // CHECK-NEXT: }, // CHECK-NEXT: { -// CHECK-NEXT: "symbol": "@funcs", -// CHECK-NEXT: "serviceName": "esi.service.std.func", -// CHECK-NEXT: "ports": [ -// CHECK-NEXT: { -// CHECK-NEXT: "name": "call", -// CHECK-NEXT: "typeID": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>" -// CHECK-NEXT: } -// CHECK-NEXT: ] -// CHECK-NEXT: }, -// CHECK-NEXT: { // CHECK-NEXT: "symbol": "@WindowService", // CHECK-NEXT: "ports": [ // CHECK-NEXT: { @@ -565,48 +544,6 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "channels": [ // CHECK-NEXT: { // CHECK-NEXT: "direction": "to", -// CHECK-NEXT: "name": "arg", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "hwBitwidth": 16, -// CHECK-NEXT: "id": "!esi.channel", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "dialect": "builtin", -// CHECK-NEXT: "hwBitwidth": 16, -// CHECK-NEXT: "id": "i16", -// CHECK-NEXT: "mnemonic": "int", -// CHECK-NEXT: "signedness": "signless" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } -// CHECK-NEXT: }, -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "from", -// CHECK-NEXT: "name": "result", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "hwBitwidth": 16, -// CHECK-NEXT: "id": "!esi.channel", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "dialect": "builtin", -// CHECK-NEXT: "hwBitwidth": 16, -// CHECK-NEXT: "id": "i16", -// CHECK-NEXT: "mnemonic": "int", -// CHECK-NEXT: "signedness": "signless" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } -// CHECK-NEXT: } -// CHECK-NEXT: ], -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "hwBitwidth": 36, -// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", -// CHECK-NEXT: "mnemonic": "bundle" -// CHECK-NEXT: }, -// CHECK-NEXT: { -// CHECK-NEXT: "channels": [ -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "to", // CHECK-NEXT: "name": "recv", // CHECK-NEXT: "type": { // CHECK-NEXT: "dialect": "esi", @@ -949,41 +886,6 @@ hw.module @top(in %clk: !seq.clock, in %rst: i1) { // CHECK-NEXT: "mnemonic": "bundle" // CHECK-NEXT: }, // CHECK-NEXT: { -// CHECK-NEXT: "channels": [ -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "to", -// CHECK-NEXT: "name": "arg", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "id": "!esi.channel", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "id": "!esi.any", -// CHECK-NEXT: "mnemonic": "any" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } -// CHECK-NEXT: }, -// CHECK-NEXT: { -// CHECK-NEXT: "direction": "from", -// CHECK-NEXT: "name": "result", -// CHECK-NEXT: "type": { -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "id": "!esi.channel", -// CHECK-NEXT: "inner": { -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "id": "!esi.any", -// CHECK-NEXT: "mnemonic": "any" -// CHECK-NEXT: }, -// CHECK-NEXT: "mnemonic": "channel" -// CHECK-NEXT: } -// CHECK-NEXT: } -// CHECK-NEXT: ], -// CHECK-NEXT: "dialect": "esi", -// CHECK-NEXT: "id": "!esi.bundle<[!esi.channel to \"arg\", !esi.channel from \"result\"]>", -// CHECK-NEXT: "mnemonic": "bundle" -// CHECK-NEXT: }, -// CHECK-NEXT: { // CHECK-NEXT: "dialect": "builtin", // CHECK-NEXT: "hwBitwidth": 64, // CHECK-NEXT: "id": "i64",