From 404a2da340abf147cac1a68c9cc942e28e72007b Mon Sep 17 00:00:00 2001 From: John Demme Date: Tue, 2 Jun 2026 18:09:20 +0000 Subject: [PATCH 1/3] [ESI] Slim down manifest There's a bunch of unnecessary stuff in ESI (in-accelerator) manifests. This commit slims it down to only the stuff which the user can actually use. Assisted-b: vscode:Claude Opus 4.8 --- lib/Dialect/ESI/Passes/ESIBuildManifest.cpp | 130 ++++++++++++++++++-- test/Dialect/ESI/manifest.mlir | 100 +-------------- 2 files changed, 119 insertions(+), 111 deletions(-) diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index 3972db7f4144..5346b6c5bd62 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 and record the absolute appID paths of all client + /// ports whose channels are bound to a host-reachable engine (i.e. which + /// appear in some engine's channel assignment table). These are the only + /// ports which are "user accessible": present in the runtime's Accelerator + /// design tree. + void collectAssignedPorts(Block &block, SmallVectorImpl &path); + + /// Walk the appID hierarchy and populate 'keepNodes' with the hierarchy nodes + /// which contain (or have a descendant which contains) a user-accessible + /// client port. Returns true if 'block' or any descendant is to be kept. + bool computeKeepNodes(Block &block, SmallVectorImpl &path); + + /// Is the client port with appID 'portID' under the node at 'nodePath' user + /// accessible? + bool isPortAccessible(ArrayRef nodePath, Attribute portID) { + SmallVector portPath(nodePath.begin(), nodePath.end()); + portPath.push_back(portID); + return assignedPortPaths.contains(ArrayAttr::get(&getContext(), portPath)); + } + // Type table. std::string useType(Type type) { std::string typeID; @@ -73,6 +96,13 @@ struct ESIBuildManifestPass DenseSet symbols; DenseSet modules; + // Absolute appID paths of user-accessible client ports (those bound to a + // host-reachable engine via a channel assignment). + DenseSet assignedPortPaths; + // AppID hierarchy nodes to retain (those whose subtree contains a + // user-accessible client port). + DenseSet keepNodes; + hw::HWSymbolCache symCache; }; } // anonymous namespace @@ -90,6 +120,16 @@ void ESIBuildManifestPass::runOnOperation() { if (!appidRoot) return; + // Determine which client ports are user-accessible and which hierarchy nodes + // need to be retained as a result. This must happen before JSONification + // since the latter relies on these sets to prune the manifest. + { + SmallVector path; + collectAssignedPorts(appidRoot.getChildren().front(), path); + path.clear(); + computeKeepNodes(appidRoot.getChildren().front(), path); + } + // JSONify the manifest. std::string jsonManifest = json(); @@ -131,8 +171,51 @@ void ESIBuildManifestPass::runOnOperation() { } } +void ESIBuildManifestPass::collectAssignedPorts( + Block &block, SmallVectorImpl &path) { + for (auto svc : block.getOps()) + for (auto client : + svc.getReqDetails().front().getOps()) { + DictionaryAttr chanAssigns = client.getChannelAssignmentsAttr(); + // A client port is only user-accessible if its channels are bound to a + // host-reachable engine, recorded via a non-empty channel assignment. + if (!chanAssigns || chanAssigns.empty()) + continue; + SmallVector portPath(path.begin(), path.end()); + for (auto id : client.getRelAppIDPath().getAsRange()) + portPath.push_back(id); + assignedPortPaths.insert(ArrayAttr::get(&getContext(), portPath)); + } + for (auto node : block.getOps()) { + path.push_back(node.getAppIDAttr()); + collectAssignedPorts(node.getChildren().front(), path); + path.pop_back(); + } +} + +bool ESIBuildManifestPass::computeKeepNodes(Block &block, + SmallVectorImpl &path) { + bool keep = false; + for (auto req : block.getOps()) + if (isPortAccessible(path, req.getRequestorAttr())) + keep = true; + for (auto node : block.getOps()) { + path.push_back(node.getAppIDAttr()); + bool childKeep = computeKeepNodes(node.getChildren().front(), path); + path.pop_back(); + if (childKeep) { + keepNodes.insert(node); + keep = true; + } + } + return keep; +} + 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 +224,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 +276,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 +302,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 +326,14 @@ 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 somewhere in the + // (pruned) design hierarchy. 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) && !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", From e27d5d24bd5e8ec8937ec01337edd910339cd7ef Mon Sep 17 00:00:00 2001 From: John Demme Date: Tue, 2 Jun 2026 19:57:06 +0000 Subject: [PATCH 2/3] fixes for some big bugs --- lib/Dialect/ESI/Passes/ESIBuildManifest.cpp | 58 +++++++++++++-------- 1 file changed, 35 insertions(+), 23 deletions(-) diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index 5346b6c5bd62..1417696a7a9f 100644 --- a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp +++ b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp @@ -59,11 +59,12 @@ struct ESIBuildManifestPass std::string json(); /// Walk the appID hierarchy and record the absolute appID paths of all client - /// ports whose channels are bound to a host-reachable engine (i.e. which - /// appear in some engine's channel assignment table). These are the only - /// ports which are "user accessible": present in the runtime's Accelerator - /// design tree. - void collectAssignedPorts(Block &block, SmallVectorImpl &path); + /// ports which are served by a service or engine (i.e. which appear in some + /// service implementation's client record). These are the only ports which + /// are "user accessible": those for which the runtime materializes a port in + /// the Accelerator design tree. Ports with no service record (e.g. host + /// memory ports, or function ports with no implementing engine) are not. + void collectServedPorts(Block &block, SmallVectorImpl &path); /// Walk the appID hierarchy and populate 'keepNodes' with the hierarchy nodes /// which contain (or have a descendant which contains) a user-accessible @@ -75,7 +76,7 @@ struct ESIBuildManifestPass bool isPortAccessible(ArrayRef nodePath, Attribute portID) { SmallVector portPath(nodePath.begin(), nodePath.end()); portPath.push_back(portID); - return assignedPortPaths.contains(ArrayAttr::get(&getContext(), portPath)); + return servedPortPaths.contains(ArrayAttr::get(&getContext(), portPath)); } // Type table. @@ -96,9 +97,14 @@ struct ESIBuildManifestPass DenseSet symbols; DenseSet modules; - // Absolute appID paths of user-accessible client ports (those bound to a - // host-reachable engine via a channel assignment). - DenseSet assignedPortPaths; + // Modules instantiated anywhere in the (full, unpruned) 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; @@ -125,7 +131,7 @@ void ESIBuildManifestPass::runOnOperation() { // since the latter relies on these sets to prune the manifest. { SmallVector path; - collectAssignedPorts(appidRoot.getChildren().front(), path); + collectServedPorts(appidRoot.getChildren().front(), path); path.clear(); computeKeepNodes(appidRoot.getChildren().front(), path); } @@ -171,24 +177,28 @@ void ESIBuildManifestPass::runOnOperation() { } } -void ESIBuildManifestPass::collectAssignedPorts( +void ESIBuildManifestPass::collectServedPorts( Block &block, SmallVectorImpl &path) { for (auto svc : block.getOps()) for (auto client : svc.getReqDetails().front().getOps()) { - DictionaryAttr chanAssigns = client.getChannelAssignmentsAttr(); - // A client port is only user-accessible if its channels are bound to a - // host-reachable engine, recorded via a non-empty channel assignment. - if (!chanAssigns || chanAssigns.empty()) - continue; + // 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); - assignedPortPaths.insert(ArrayAttr::get(&getContext(), portPath)); + servedPortPaths.insert(ArrayAttr::get(&getContext(), portPath)); } for (auto node : block.getOps()) { + // Record every module instantiated in the (full, unpruned) design + // hierarchy so that its metadata (module_infos) is always retained, even + // when the instance's subtree is pruned from the design tree because it + // contains no user-accessible ports. + instantiatedModules.insert(node.getModuleRefAttr()); path.push_back(node.getAppIDAttr()); - collectAssignedPorts(node.getChildren().front(), path); + collectServedPorts(node.getChildren().front(), path); path.pop_back(); } } @@ -328,12 +338,14 @@ std::string ESIBuildManifestPass::json() { FlatSymbolRefAttr sym = symInfo.getSymbolRefAttr(); if (!sym) continue; - // Keep a module's metadata if it is instantiated somewhere in the - // (pruned) design hierarchy. Additionally, always keep module constants: - // they are semantically significant (e.g. for codegen) even when the - // module itself is not user-accessible. + // 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) && !isConstants) + if (!symbols.contains(sym) && !instantiatedModules.contains(sym) && + !isConstants) continue; symbolInfoLookup[sym].push_back(symInfo); } From 21c6745c973f57dd96816ae7f1ad499be5d724d0 Mon Sep 17 00:00:00 2001 From: John Demme Date: Wed, 3 Jun 2026 00:02:55 +0000 Subject: [PATCH 3/3] cleanups --- lib/Dialect/ESI/Passes/ESIBuildManifest.cpp | 142 ++++++++++++-------- 1 file changed, 84 insertions(+), 58 deletions(-) diff --git a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp index 1417696a7a9f..2c05c725d106 100644 --- a/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp +++ b/lib/Dialect/ESI/Passes/ESIBuildManifest.cpp @@ -58,25 +58,24 @@ struct ESIBuildManifestPass /// Get a JSON representation of the manifest. std::string json(); - /// Walk the appID hierarchy and record the absolute appID paths of all client - /// ports which are served by a service or engine (i.e. which appear in some - /// service implementation's client record). These are the only ports which - /// are "user accessible": those for which the runtime materializes a port in - /// the Accelerator design tree. Ports with no service record (e.g. host - /// memory ports, or function ports with no implementing engine) are not. - void collectServedPorts(Block &block, SmallVectorImpl &path); + /// 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. Returns true if 'block' or any descendant is to be kept. - bool computeKeepNodes(Block &block, SmallVectorImpl &path); + /// 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) { + bool isPortAccessible(ArrayRef nodePath, Attribute portID) const { SmallVector portPath(nodePath.begin(), nodePath.end()); portPath.push_back(portID); - return servedPortPaths.contains(ArrayAttr::get(&getContext(), portPath)); + return servedPortPaths.contains( + ArrayAttr::get(portID.getContext(), portPath)); } // Type table. @@ -97,9 +96,9 @@ struct ESIBuildManifestPass DenseSet symbols; DenseSet modules; - // Modules instantiated anywhere in the (full, unpruned) 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. + // 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 @@ -127,14 +126,9 @@ void ESIBuildManifestPass::runOnOperation() { return; // Determine which client ports are user-accessible and which hierarchy nodes - // need to be retained as a result. This must happen before JSONification - // since the latter relies on these sets to prune the manifest. - { - SmallVector path; - collectServedPorts(appidRoot.getChildren().front(), path); - path.clear(); - computeKeepNodes(appidRoot.getChildren().front(), path); - } + // need to be retained as a result. + collectAccessibilityInfo(appidRoot.getChildren().front()); + computeKeepNodes(appidRoot.getChildren().front()); // JSONify the manifest. std::string jsonManifest = json(); @@ -177,48 +171,80 @@ void ESIBuildManifestPass::runOnOperation() { } } -void ESIBuildManifestPass::collectServedPorts( - Block &block, SmallVectorImpl &path) { - 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()) { - // Record every module instantiated in the (full, unpruned) design - // hierarchy so that its metadata (module_infos) is always retained, even - // when the instance's subtree is pruned from the design tree because it - // contains no user-accessible ports. +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()); - path.push_back(node.getAppIDAttr()); - collectServedPorts(node.getChildren().front(), path); - path.pop_back(); + }); + + // 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)); + } } } -bool ESIBuildManifestPass::computeKeepNodes(Block &block, - SmallVectorImpl &path) { - bool keep = false; - for (auto req : block.getOps()) - if (isPortAccessible(path, req.getRequestorAttr())) - keep = true; - for (auto node : block.getOps()) { - path.push_back(node.getAppIDAttr()); - bool childKeep = computeKeepNodes(node.getChildren().front(), path); - path.pop_back(); - if (childKeep) { - keepNodes.insert(node); - keep = true; +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); } - return keep; } void ESIBuildManifestPass::emitNode(llvm::json::OStream &j,