MLIR-AIE
AIEAssignCoreLinkFiles.cpp
Go to the documentation of this file.
1//===- AIEAssignCoreLinkFiles.cpp -------------------------------*- C++ -*-===//
2//
3// This file is licensed under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7// (c) Copyright 2026 Advanced Micro Devices Inc.
8//
9//===----------------------------------------------------------------------===//
10//
11// This pass infers the per-core set of external object files required for
12// linking by tracing call edges from each core to func.func declarations that
13// carry a "link_with" attribute.
14//
15// After the pass runs, every CoreOp that needs external files will have a
16// "link_files" StrArrayAttr containing the (de-duplicated) list of .o paths.
17//
18// Core-level "link_with" (deprecated) is also migrated: its value is added to
19// the set and the attribute is removed from the CoreOp.
20//
21//===----------------------------------------------------------------------===//
22
24#define GEN_PASS_DEF_AIEASSIGNCORELINKFILES
26
27#include "mlir/Dialect/Func/IR/FuncOps.h"
28#include "mlir/IR/Builders.h"
29#include "mlir/Pass/Pass.h"
30
31#include "llvm/ADT/DenseSet.h"
32#include "llvm/ADT/SetVector.h"
33
34#define DEBUG_TYPE "aie-assign-core-link-files"
35
36using namespace mlir;
37using namespace xilinx;
38using namespace xilinx::AIE;
39
42 AIEAssignCoreLinkFilesPass> {
43 void runOnOperation() override {
44 DeviceOp device = getOperation();
45 // Builder is used only for attribute construction; no ops are inserted.
46 Builder builder(device.getContext());
47
48 // Build a map from func name to the object file(s) it requires, sourced
49 // from the "link_with" string attribute on func.func declarations.
50 // StringRefs are views into MLIRContext-owned storage and remain valid
51 // for the entire pass run.
52 DenseMap<StringRef, SmallVector<StringRef, 2>> funcToObjs;
53 for (auto funcOp : device.getOps<mlir::func::FuncOp>()) {
54 if (auto attr = funcOp->getAttrOfType<mlir::StringAttr>("link_with")) {
55 funcToObjs[funcOp.getName()].push_back(attr.getValue());
56 }
57 }
58
59 // Tracks which func.func symbols are directly called from at least one
60 // core; used to warn about link_with-bearing functions that are never
61 // called and whose object files would otherwise be silently omitted.
62 llvm::DenseSet<StringRef> usedFuncs;
63
64 // Only direct func.call edges are traced. func.call_indirect ops and
65 // calls through intermediate wrapper functions are not followed. To
66 // handle transitive dependencies, attach link_with directly to every
67 // func.func declaration that a core calls, even thin wrappers.
68 // TODO: extend to transitive call resolution.
69 device.walk([&](CoreOp core) {
70 // De-duplicate while preserving insertion order.
71 llvm::SetVector<StringRef> needed;
72
73 // Migrate deprecated core-level attr: warn, consume it, and add to set.
74 if (auto lw = core.getLinkWith()) {
75 core.emitWarning(
76 "link_with on aie.core is deprecated; attach link_with to "
77 "the func.func declaration instead");
78 needed.insert(lw.value());
79 core->removeAttr("link_with");
80 }
81
82 // Single walk over the core body: collect required object files and
83 // record called symbols (for the unused-func warning below).
84 core.walk([&](Operation *op) {
85 if (auto call = dyn_cast<mlir::func::CallOp>(op)) {
86 usedFuncs.insert(call.getCallee());
87 auto it = funcToObjs.find(call.getCallee());
88 if (it != funcToObjs.end())
89 for (StringRef obj : it->second)
90 needed.insert(obj);
91 } else if (auto indCall = dyn_cast<mlir::func::CallIndirectOp>(op)) {
92 indCall.emitWarning(
93 "indirect call in core body — link_with attributes on "
94 "indirectly-called functions are not automatically resolved; "
95 "add a direct func.call to the required func.func declaration "
96 "so that aie-assign-core-link-files can trace the dependency");
97 }
98 });
99
100 if (!needed.empty()) {
101 // builder is used only for attribute construction; its insertion
102 // point is irrelevant and no ops are inserted.
103 core.setLinkFilesAttr(builder.getStrArrayAttr(needed.getArrayRef()));
104 }
105 });
106
107 // Warn about funcs with link_with that are never called from any core.
108 for (auto &[funcName, objs] : funcToObjs) {
109 if (!usedFuncs.count(funcName)) {
110 if (auto funcOp = device.lookupSymbol<mlir::func::FuncOp>(funcName))
111 funcOp.emitWarning()
112 << "func '" << funcName
113 << "' has link_with but is never called from any core; "
114 "its .o file will not be linked";
115 }
116 }
117 }
118};
119
120std::unique_ptr<OperationPass<DeviceOp>>
122 return std::make_unique<AIEAssignCoreLinkFilesPass>();
123}
Include the generated interface declarations.
std::unique_ptr< mlir::OperationPass< DeviceOp > > createAIEAssignCoreLinkFilesPass()