MLIR 23.0.0git
OffloadTargetVerifier.cpp
Go to the documentation of this file.
1//===- OffloadTargetVerifier.cpp ------------------------------------------===//
2//
3// Part of the LLVM Project, 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//===----------------------------------------------------------------------===//
8//
9// This pass verifies that values and symbols used within offload regions are
10// legal for the target execution model.
11//
12// Overview:
13// ---------
14// Offload regions execute on a target device (e.g., GPU) where not all values
15// and symbols from the host context are accessible. This pass checks that
16// live-in values (values defined outside but used inside the region) and
17// symbol references are valid for device execution.
18//
19// The pass operates on any operation implementing `OffloadRegionOpInterface`,
20// which includes OpenACC compute constructs (`acc.parallel`, `acc.kernels`,
21// `acc.serial`) as well as GPU operations like `gpu.launch`.
22//
23// Verification:
24// -------------
25// For each offload region, the pass checks:
26//
27// 1. Live-in Values: Values flowing into the region must be valid for device
28// use. This includes checking that data has been properly mapped via
29// OpenACC data clauses (copyin, copyout, present, etc.) or is a scalar
30// that can be passed by value.
31//
32// 2. Symbol References: Symbols referenced inside the region must be
33// accessible on the device. This includes checking for proper `declare`
34// attributes on globals or device-resident data attributes.
35//
36// Requirements:
37// -------------
38// 1. Target Region Identification: Operations representing offload regions
39// must implement `acc::OffloadRegionOpInterface`.
40//
41// 2. OpenACCSupport Analysis: The pass relies on the `OpenACCSupport`
42// analysis to determine value and symbol validity. This analysis provides
43// dialect-specific hooks for checking legality through `isValidValueUse`
44// and `isValidSymbolUse` methods. Custom dialect support can be registered
45// by providing a derived `OpenACCSupport` analysis before running this
46// pass.
47//
48// 3. Device Type: The `device_type` option specifies the target device.
49// For `host` or `multicore` targets, verification of ACC compute
50// constructs is not yet implemented.
51//
52//===----------------------------------------------------------------------===//
53
59#include "mlir/IR/SymbolTable.h"
60#include "llvm/Support/Debug.h"
61
62namespace mlir {
63namespace acc {
64#define GEN_PASS_DEF_OFFLOADTARGETVERIFIER
65#include "mlir/Dialect/OpenACC/Transforms/Passes.h.inc"
66} // namespace acc
67} // namespace mlir
68
69#define DEBUG_TYPE "offload-target-verifier"
70
71using namespace mlir;
72
73namespace {
74
75class OffloadTargetVerifier
76 : public acc::impl::OffloadTargetVerifierBase<OffloadTargetVerifier> {
77public:
78 using OffloadTargetVerifierBase::OffloadTargetVerifierBase;
79
80 /// Returns true if the target device type corresponds to host execution.
81 bool isHostTarget() const {
82 return deviceType == acc::DeviceType::Host ||
83 deviceType == acc::DeviceType::Multicore;
84 }
85
86 /// Check live-in values for legality.
88 getIllegalLiveInValues(Region &region, Liveness &liveness,
89 acc::OpenACCSupport &accSupport) const {
90 auto isInvalid = [&](Value val) -> bool {
91 return !accSupport.isValidValueUse(val, region);
92 };
93
94 SmallVector<Value> illegalValues(llvm::make_filter_range(
95 liveness.getLiveIn(&region.front()), isInvalid));
96
97 return illegalValues;
98 }
99
100 /// Check symbol uses for legality.
102 getIllegalUsedSymbols(Region &region, acc::OpenACCSupport &accSupport) const {
103 auto symUses = SymbolTable::getSymbolUses(&region);
104
105 // When there are no symbols used in the region, there are no illegal ones.
106 if (!symUses.has_value())
107 return {};
108
109 auto isInvalidSymbol = [&](const SymbolTable::SymbolUse &symUse) -> bool {
110 Operation *definingOp = nullptr;
111 return !accSupport.isValidSymbolUse(symUse.getUser(),
112 symUse.getSymbolRef(), &definingOp);
113 };
114
115 auto invalidSyms =
116 llvm::make_filter_range(symUses.value(), isInvalidSymbol);
117 SmallVector<SymbolTable::SymbolUse> invalidSymsList(invalidSyms);
118 return invalidSymsList;
119 }
120
121 /// Check if the region has illegal live-in values.
122 bool hasIllegalLiveInValues(Operation *regionOp,
123 acc::OpenACCSupport &accSupport) const {
124 if (regionOp->getNumRegions() == 0)
125 return false;
126
127 Liveness liveness(regionOp);
128 SmallVector<Value> invalidValues =
129 getIllegalLiveInValues(regionOp->getRegion(0), liveness, accSupport);
130
131 bool hasIllegalValues = !invalidValues.empty();
132
133 if (hasIllegalValues) {
134 if (softCheck) {
135 // Emit warnings for each illegal value.
136 auto diag = regionOp->emitWarning("offload target verifier: ")
137 << invalidValues.size() << " illegal live-in value(s)";
138 for (auto [idx, invalidValue] : llvm::enumerate(invalidValues)) {
139 diag.attachNote(invalidValue.getLoc()) << "value: " << invalidValue;
140 }
141 } else {
142 accSupport.emitNYI(regionOp->getLoc(),
143 "offload target verifier failed due to " +
144 Twine(invalidValues.size()) +
145 " illegal live-in value(s)");
146 }
147 }
148
149 return hasIllegalValues;
150 }
151
152 /// Check if the region has illegal symbol uses.
153 bool hasIllegalSymbolUses(Operation *regionOp,
154 acc::OpenACCSupport &accSupport) const {
155 if (regionOp->getNumRegions() == 0)
156 return false;
157
159 getIllegalUsedSymbols(regionOp->getRegion(0), accSupport);
160
161 bool hasIllegalSymbols = !invalidSyms.empty();
162
163 if (hasIllegalSymbols) {
164 auto getSymName = [&](SymbolTable::SymbolUse symUse) -> std::string {
165 return symUse.getSymbolRef().getLeafReference().str();
166 };
167 std::string invalidString =
168 llvm::join(llvm::map_range(invalidSyms, getSymName), ", ");
169
170 // Emit only warnings when softCheck is enabled.
171 if (softCheck)
172 regionOp->emitWarning("offload target verifier: illegal symbol(s): ")
173 << invalidString;
174 else
175 accSupport.emitNYI(regionOp->getLoc(),
176 "offload target verifier failed due to illegal "
177 "symbol(s): " +
178 invalidString);
179 }
180
181 return hasIllegalSymbols;
182 }
183
184 void runOnOperation() override {
185 LLVM_DEBUG(llvm::dbgs() << "Enter OffloadTargetVerifier()\n");
186 func::FuncOp func = getOperation();
187
188 // Try to get cached parent analysis first, fall back to local analysis.
189 auto cachedAnalysis =
190 getCachedParentAnalysis<acc::OpenACCSupport>(func->getParentOp());
191 acc::OpenACCSupport &accSupport = cachedAnalysis
192 ? cachedAnalysis->get()
193 : getAnalysis<acc::OpenACCSupport>();
194
195 bool hasErrors = false;
196
197 func.walk([&](Operation *op) {
198 // Only process offload region operations.
199 if (!isa<acc::OffloadRegionOpInterface>(op))
200 return WalkResult::advance();
201
202 // TODO: Host/multicore verification for ACC compute constructs is not yet
203 // implemented.
204 if (isHostTarget() && isa<ACC_COMPUTE_CONSTRUCT_OPS>(op)) {
205 accSupport.emitNYI(op->getLoc(),
206 "host/multicore verification for ACC compute "
207 "constructs");
208 return WalkResult::advance();
209 }
210
211 // Check for illegal live-in values.
212 bool hasIllegalValues = hasIllegalLiveInValues(op, accSupport);
213 if (hasIllegalValues)
214 hasErrors = true;
215
216 // Check for illegal symbol uses.
217 bool hasIllegalSyms = hasIllegalSymbolUses(op, accSupport);
218 if (hasIllegalSyms)
219 hasErrors = true;
220
221 if (!hasIllegalValues && !hasIllegalSyms && softCheck)
222 op->emitRemark("offload target verifier: passed validity check");
223
224 return WalkResult::advance();
225 });
226
227 if (hasErrors && !softCheck)
228 signalPassFailure();
229
230 LLVM_DEBUG(llvm::dbgs() << "Exit OffloadTargetVerifier()\n");
231 }
232};
233
234} // namespace
static std::string diag(const llvm::Value &value)
Represents an analysis for computing liveness information from a given top-level operation.
Definition Liveness.h:47
const ValueSetT & getLiveIn(Block *block) const
Returns a reference to a set containing live-in values (unordered).
Definition Liveness.cpp:231
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
Region & getRegion(unsigned index)
Returns the region held by this operation at position 'index'.
Definition Operation.h:686
InFlightDiagnostic emitWarning(const Twine &message={})
Emit a warning about this operation, reporting up to any diagnostic handlers that may be listening.
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:674
Location getLoc()
The source location the operation was defined or derived from.
Definition Operation.h:223
InFlightDiagnostic emitRemark(const Twine &message={})
Emit a remark about this operation, reporting up to any diagnostic handlers that may be listening.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
Block & front()
Definition Region.h:65
This class represents a specific symbol use.
static std::optional< UseRange > getSymbolUses(Operation *from)
Get an iterator range for all of the uses, for any symbol, that are nested within the given operation...
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
static WalkResult advance()
Definition WalkResult.h:47
InFlightDiagnostic emitNYI(Location loc, const Twine &message)
Report a case that is not yet supported by the implementation.
bool isValidValueUse(Value v, Region &region)
Check if a value use is legal in an OpenACC region.
bool isValidSymbolUse(Operation *user, SymbolRefAttr symbol, Operation **definingOpPtr=nullptr)
Check if a symbol use is valid for use in an OpenACC region.
Include the generated interface declarations.