MLIR 22.0.0git
ControlFlowInterfaces.cpp
Go to the documentation of this file.
1//===- ControlFlowInterfaces.cpp - ControlFlow Interfaces -----------------===//
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#include <utility>
10
12#include "mlir/IR/Operation.h"
14#include "llvm/Support/DebugLog.h"
15
16using namespace mlir;
17
18//===----------------------------------------------------------------------===//
19// ControlFlowInterfaces
20//===----------------------------------------------------------------------===//
21
22#include "mlir/Interfaces/ControlFlowInterfaces.cpp.inc"
23
25 : producedOperandCount(0), forwardedOperands(std::move(forwardedOperands)) {
26}
27
28SuccessorOperands::SuccessorOperands(unsigned int producedOperandCount,
29 MutableOperandRange forwardedOperands)
30 : producedOperandCount(producedOperandCount),
31 forwardedOperands(std::move(forwardedOperands)) {}
32
33//===----------------------------------------------------------------------===//
34// BranchOpInterface
35//===----------------------------------------------------------------------===//
36
37/// Returns the `BlockArgument` corresponding to operand `operandIndex` in some
38/// successor if 'operandIndex' is within the range of 'operands', or
39/// std::nullopt if `operandIndex` isn't a successor operand index.
40std::optional<BlockArgument>
42 unsigned operandIndex, Block *successor) {
43 LDBG() << "Getting branch successor argument for operand index "
44 << operandIndex << " in successor block";
45
46 OperandRange forwardedOperands = operands.getForwardedOperands();
47 // Check that the operands are valid.
48 if (forwardedOperands.empty()) {
49 LDBG() << "No forwarded operands, returning nullopt";
50 return std::nullopt;
51 }
52
53 // Check to ensure that this operand is within the range.
54 unsigned operandsStart = forwardedOperands.getBeginOperandIndex();
55 if (operandIndex < operandsStart ||
56 operandIndex >= (operandsStart + forwardedOperands.size())) {
57 LDBG() << "Operand index " << operandIndex << " out of range ["
58 << operandsStart << ", "
59 << (operandsStart + forwardedOperands.size())
60 << "), returning nullopt";
61 return std::nullopt;
62 }
63
64 // Index the successor.
65 unsigned argIndex =
66 operands.getProducedOperandCount() + operandIndex - operandsStart;
67 LDBG() << "Computed argument index " << argIndex << " for successor block";
68 return successor->getArgument(argIndex);
69}
70
71/// Verify that the given operands match those of the given successor block.
72LogicalResult
74 const SuccessorOperands &operands) {
75 LDBG() << "Verifying branch successor operands for successor #" << succNo
76 << " in operation " << op->getName();
77
78 // Check the count.
79 unsigned operandCount = operands.size();
80 Block *destBB = op->getSuccessor(succNo);
81 LDBG() << "Branch has " << operandCount << " operands, target block has "
82 << destBB->getNumArguments() << " arguments";
83
84 if (operandCount != destBB->getNumArguments())
85 return op->emitError() << "branch has " << operandCount
86 << " operands for successor #" << succNo
87 << ", but target block has "
88 << destBB->getNumArguments();
89
90 // Check the types.
91 LDBG() << "Checking type compatibility for "
92 << (operandCount - operands.getProducedOperandCount())
93 << " forwarded operands";
94 for (unsigned i = operands.getProducedOperandCount(); i != operandCount;
95 ++i) {
96 Type operandType = operands[i].getType();
97 Type argType = destBB->getArgument(i).getType();
98 LDBG() << "Checking type compatibility: operand type " << operandType
99 << " vs argument type " << argType;
100
101 if (!cast<BranchOpInterface>(op).areTypesCompatible(operandType, argType))
102 return op->emitError() << "type mismatch for bb argument #" << i
103 << " of successor #" << succNo;
104 }
105
106 LDBG() << "Branch successor operand verification successful";
107 return success();
108}
109
110//===----------------------------------------------------------------------===//
111// WeightedBranchOpInterface
112//===----------------------------------------------------------------------===//
113
114static LogicalResult verifyWeights(Operation *op,
116 std::size_t expectedWeightsNum,
117 llvm::StringRef weightAnchorName,
118 llvm::StringRef weightRefName) {
119 if (weights.empty())
120 return success();
121
122 if (weights.size() != expectedWeightsNum)
123 return op->emitError() << "expects number of " << weightAnchorName
124 << " weights to match number of " << weightRefName
125 << ": " << weights.size() << " vs "
126 << expectedWeightsNum;
127
128 if (llvm::all_of(weights, [](int32_t value) { return value == 0; }))
129 return op->emitError() << "branch weights cannot all be zero";
130
131 return success();
132}
133
136 cast<WeightedBranchOpInterface>(op).getWeights();
137 return verifyWeights(op, weights, op->getNumSuccessors(), "branch",
138 "successors");
139}
140
141//===----------------------------------------------------------------------===//
142// WeightedRegionBranchOpInterface
143//===----------------------------------------------------------------------===//
144
147 cast<WeightedRegionBranchOpInterface>(op).getWeights();
148 return verifyWeights(op, weights, op->getNumRegions(), "region", "regions");
149}
150
151//===----------------------------------------------------------------------===//
152// RegionBranchOpInterface
153//===----------------------------------------------------------------------===//
154
156 RegionBranchPoint sourceNo,
157 RegionSuccessor succRegionNo) {
158 diag << "from ";
159 if (Operation *op = sourceNo.getTerminatorPredecessorOrNull())
160 diag << "Operation " << op->getName();
161 else
162 diag << "parent operands";
163
164 diag << " to ";
165 if (Region *region = succRegionNo.getSuccessor())
166 diag << "Region #" << region->getRegionNumber();
167 else
168 diag << "parent results";
169 return diag;
170}
171
172/// Verify that types match along all region control flow edges originating from
173/// `sourcePoint`. `getInputsTypesForRegion` is a function that returns the
174/// types of the inputs that flow to a successor region.
175static LogicalResult
176verifyTypesAlongAllEdges(RegionBranchOpInterface branchOp,
177 RegionBranchPoint sourcePoint,
178 function_ref<FailureOr<TypeRange>(RegionSuccessor)>
179 getInputsTypesForRegion) {
181 branchOp.getSuccessorRegions(sourcePoint, successors);
182
183 for (RegionSuccessor &succ : successors) {
184 FailureOr<TypeRange> sourceTypes = getInputsTypesForRegion(succ);
185 if (failed(sourceTypes))
186 return failure();
187
188 TypeRange succInputsTypes = succ.getSuccessorInputs().getTypes();
189 if (sourceTypes->size() != succInputsTypes.size()) {
191 branchOp->emitOpError("region control flow edge ");
192 std::string succStr;
193 llvm::raw_string_ostream os(succStr);
194 os << succ;
195 return printRegionEdgeName(diag, sourcePoint, succ)
196 << ": source has " << sourceTypes->size()
197 << " operands, but target successor " << os.str() << " needs "
198 << succInputsTypes.size();
199 }
200
201 for (const auto &typesIdx :
202 llvm::enumerate(llvm::zip(*sourceTypes, succInputsTypes))) {
203 Type sourceType = std::get<0>(typesIdx.value());
204 Type inputType = std::get<1>(typesIdx.value());
205
206 if (!branchOp.areTypesCompatible(sourceType, inputType)) {
208 branchOp->emitOpError("along control flow edge ");
209 return printRegionEdgeName(diag, sourcePoint, succ)
210 << ": source type #" << typesIdx.index() << " " << sourceType
211 << " should match input type #" << typesIdx.index() << " "
212 << inputType;
213 }
214 }
215 }
216
217 return success();
218}
219
220/// Verify that types match along control flow edges described the given op.
222 auto regionInterface = cast<RegionBranchOpInterface>(op);
223
224 auto inputTypesFromParent = [&](RegionSuccessor successor) -> TypeRange {
225 return regionInterface.getEntrySuccessorOperands(successor).getTypes();
226 };
227
228 // Verify types along control flow edges originating from the parent.
229 if (failed(verifyTypesAlongAllEdges(
230 regionInterface, RegionBranchPoint::parent(), inputTypesFromParent)))
231 return failure();
232
233 // Verify types along control flow edges originating from each region.
234 for (Region &region : op->getRegions()) {
235 // Collect all return-like terminators in the region.
237 for (Block &block : region)
238 if (!block.empty())
239 if (auto terminator =
240 dyn_cast<RegionBranchTerminatorOpInterface>(block.back()))
241 regionReturnOps.push_back(terminator);
242
243 // If there is no return-like terminator, the op itself should verify
244 // type consistency.
245 if (regionReturnOps.empty())
246 continue;
247
248 // Verify types along control flow edges originating from each return-like
249 // terminator.
250 for (RegionBranchTerminatorOpInterface regionReturnOp : regionReturnOps) {
251
252 auto inputTypesForRegion =
253 [&](RegionSuccessor successor) -> FailureOr<TypeRange> {
254 OperandRange terminatorOperands =
255 regionReturnOp.getSuccessorOperands(successor);
256 return TypeRange(terminatorOperands.getTypes());
257 };
258 if (failed(verifyTypesAlongAllEdges(regionInterface, regionReturnOp,
259 inputTypesForRegion)))
260 return failure();
261 }
262 }
263
264 return success();
265}
266
267/// Stop condition for `traverseRegionGraph`. The traversal is interrupted if
268/// this function returns "true" for a successor region. The first parameter is
269/// the successor region. The second parameter indicates all already visited
270/// regions.
272
273/// Traverse the region graph starting at `begin`. The traversal is interrupted
274/// if `stopCondition` evaluates to "true" for a successor region. In that case,
275/// this function returns "true". Otherwise, if the traversal was not
276/// interrupted, this function returns "false".
277static bool traverseRegionGraph(Region *begin,
278 StopConditionFn stopConditionFn) {
279 auto op = cast<RegionBranchOpInterface>(begin->getParentOp());
280 LDBG() << "Starting region graph traversal from region #"
281 << begin->getRegionNumber() << " in operation " << op->getName();
282
283 SmallVector<bool> visited(op->getNumRegions(), false);
284 visited[begin->getRegionNumber()] = true;
285 LDBG() << "Initialized visited array with " << op->getNumRegions()
286 << " regions";
287
288 // Retrieve all successors of the region and enqueue them in the worklist.
289 SmallVector<Region *> worklist;
290 auto enqueueAllSuccessors = [&](Region *region) {
291 LDBG() << "Enqueuing successors for region #" << region->getRegionNumber();
292 SmallVector<Attribute> operandAttributes(op->getNumOperands());
293 for (Block &block : *region) {
294 if (block.empty())
295 continue;
296 auto terminator =
297 dyn_cast<RegionBranchTerminatorOpInterface>(block.back());
298 if (!terminator)
299 continue;
301 operandAttributes.resize(terminator->getNumOperands());
302 terminator.getSuccessorRegions(operandAttributes, successors);
303 LDBG() << "Found " << successors.size()
304 << " successors from terminator in block";
305 for (RegionSuccessor successor : successors) {
306 if (!successor.isParent()) {
307 worklist.push_back(successor.getSuccessor());
308 LDBG() << "Added region #"
309 << successor.getSuccessor()->getRegionNumber()
310 << " to worklist";
311 } else {
312 LDBG() << "Skipping parent successor";
313 }
314 }
315 }
316 };
317 enqueueAllSuccessors(begin);
318 LDBG() << "Initial worklist size: " << worklist.size();
319
320 // Process all regions in the worklist via DFS.
321 while (!worklist.empty()) {
322 Region *nextRegion = worklist.pop_back_val();
323 LDBG() << "Processing region #" << nextRegion->getRegionNumber()
324 << " from worklist (remaining: " << worklist.size() << ")";
325
326 if (stopConditionFn(nextRegion, visited)) {
327 LDBG() << "Stop condition met for region #"
328 << nextRegion->getRegionNumber() << ", returning true";
329 return true;
330 }
331 if (!nextRegion->getParentOp()) {
332 llvm::errs() << "Region " << *nextRegion << " has no parent op\n";
333 return false;
334 }
335 if (visited[nextRegion->getRegionNumber()]) {
336 LDBG() << "Region #" << nextRegion->getRegionNumber()
337 << " already visited, skipping";
338 continue;
339 }
340 visited[nextRegion->getRegionNumber()] = true;
341 LDBG() << "Marking region #" << nextRegion->getRegionNumber()
342 << " as visited";
343 enqueueAllSuccessors(nextRegion);
344 }
345
346 LDBG() << "Traversal completed, returning false";
347 return false;
348}
349
350/// Return `true` if region `r` is reachable from region `begin` according to
351/// the RegionBranchOpInterface (by taking a branch).
352static bool isRegionReachable(Region *begin, Region *r) {
353 assert(begin->getParentOp() == r->getParentOp() &&
354 "expected that both regions belong to the same op");
355 return traverseRegionGraph(begin,
356 [&](Region *nextRegion, ArrayRef<bool> visited) {
357 // Interrupt traversal if `r` was reached.
358 return nextRegion == r;
359 });
360}
361
362/// Return `true` if `a` and `b` are in mutually exclusive regions.
363///
364/// 1. Find the first common of `a` and `b` (ancestor) that implements
365/// RegionBranchOpInterface.
366/// 2. Determine the regions `regionA` and `regionB` in which `a` and `b` are
367/// contained.
368/// 3. Check if `regionA` and `regionB` are mutually exclusive. They are
369/// mutually exclusive if they are not reachable from each other as per
370/// RegionBranchOpInterface::getSuccessorRegions.
372 LDBG() << "Checking if operations are in mutually exclusive regions: "
373 << a->getName() << " and " << b->getName();
374
375 assert(a && "expected non-empty operation");
376 assert(b && "expected non-empty operation");
377
378 auto branchOp = a->getParentOfType<RegionBranchOpInterface>();
379 while (branchOp) {
380 LDBG() << "Checking branch operation " << branchOp->getName();
381
382 // Check if b is inside branchOp. (We already know that a is.)
383 if (!branchOp->isProperAncestor(b)) {
384 LDBG() << "Operation b is not inside branchOp, checking next ancestor";
385 // Check next enclosing RegionBranchOpInterface.
386 branchOp = branchOp->getParentOfType<RegionBranchOpInterface>();
387 continue;
388 }
389
390 LDBG() << "Both operations are inside branchOp, finding their regions";
391
392 // b is contained in branchOp. Retrieve the regions in which `a` and `b`
393 // are contained.
394 Region *regionA = nullptr, *regionB = nullptr;
395 for (Region &r : branchOp->getRegions()) {
396 if (r.findAncestorOpInRegion(*a)) {
397 assert(!regionA && "already found a region for a");
398 regionA = &r;
399 LDBG() << "Found region #" << r.getRegionNumber() << " for operation a";
400 }
401 if (r.findAncestorOpInRegion(*b)) {
402 assert(!regionB && "already found a region for b");
403 regionB = &r;
404 LDBG() << "Found region #" << r.getRegionNumber() << " for operation b";
405 }
406 }
407 assert(regionA && regionB && "could not find region of op");
408
409 LDBG() << "Region A: #" << regionA->getRegionNumber() << ", Region B: #"
410 << regionB->getRegionNumber();
411
412 // `a` and `b` are in mutually exclusive regions if both regions are
413 // distinct and neither region is reachable from the other region.
414 bool regionsAreDistinct = (regionA != regionB);
415 bool aNotReachableFromB = !isRegionReachable(regionA, regionB);
416 bool bNotReachableFromA = !isRegionReachable(regionB, regionA);
417
418 LDBG() << "Regions distinct: " << regionsAreDistinct
419 << ", A not reachable from B: " << aNotReachableFromB
420 << ", B not reachable from A: " << bNotReachableFromA;
421
422 bool mutuallyExclusive =
423 regionsAreDistinct && aNotReachableFromB && bNotReachableFromA;
424 LDBG() << "Operations are mutually exclusive: " << mutuallyExclusive;
425
426 return mutuallyExclusive;
427 }
428
429 // Could not find a common RegionBranchOpInterface among a's and b's
430 // ancestors.
431 LDBG() << "No common RegionBranchOpInterface found, operations are not "
432 "mutually exclusive";
433 return false;
434}
435
436bool RegionBranchOpInterface::isRepetitiveRegion(unsigned index) {
437 LDBG() << "Checking if region #" << index << " is repetitive in operation "
438 << getOperation()->getName();
439
440 Region *region = &getOperation()->getRegion(index);
441 bool isRepetitive = isRegionReachable(region, region);
442
443 LDBG() << "Region #" << index << " is repetitive: " << isRepetitive;
444 return isRepetitive;
445}
446
447bool RegionBranchOpInterface::hasLoop() {
448 LDBG() << "Checking if operation " << getOperation()->getName()
449 << " has loops";
450
451 SmallVector<RegionSuccessor> entryRegions;
452 getSuccessorRegions(RegionBranchPoint::parent(), entryRegions);
453 LDBG() << "Found " << entryRegions.size() << " entry regions";
454
455 for (RegionSuccessor successor : entryRegions) {
456 if (!successor.isParent()) {
457 LDBG() << "Checking entry region #"
458 << successor.getSuccessor()->getRegionNumber() << " for loops";
459
460 bool hasLoop =
461 traverseRegionGraph(successor.getSuccessor(),
462 [](Region *nextRegion, ArrayRef<bool> visited) {
463 // Interrupt traversal if the region was already
464 // visited.
465 return visited[nextRegion->getRegionNumber()];
466 });
467
468 if (hasLoop) {
469 LDBG() << "Found loop in entry region #"
470 << successor.getSuccessor()->getRegionNumber();
471 return true;
472 }
473 } else {
474 LDBG() << "Skipping parent successor";
475 }
476 }
477
478 LDBG() << "No loops found in operation";
479 return false;
480}
481
483 LDBG() << "Finding enclosing repetitive region for operation "
484 << op->getName();
485
486 while (Region *region = op->getParentRegion()) {
487 LDBG() << "Checking region #" << region->getRegionNumber()
488 << " in operation " << region->getParentOp()->getName();
489
490 op = region->getParentOp();
491 if (auto branchOp = dyn_cast<RegionBranchOpInterface>(op)) {
492 LDBG()
493 << "Found RegionBranchOpInterface, checking if region is repetitive";
494 if (branchOp.isRepetitiveRegion(region->getRegionNumber())) {
495 LDBG() << "Found repetitive region #" << region->getRegionNumber();
496 return region;
497 }
498 } else {
499 LDBG() << "Parent operation does not implement RegionBranchOpInterface";
500 }
501 }
502
503 LDBG() << "No enclosing repetitive region found";
504 return nullptr;
505}
506
508 LDBG() << "Finding enclosing repetitive region for value";
509
510 Region *region = value.getParentRegion();
511 while (region) {
512 LDBG() << "Checking region #" << region->getRegionNumber()
513 << " in operation " << region->getParentOp()->getName();
514
515 Operation *op = region->getParentOp();
516 if (auto branchOp = dyn_cast<RegionBranchOpInterface>(op)) {
517 LDBG()
518 << "Found RegionBranchOpInterface, checking if region is repetitive";
519 if (branchOp.isRepetitiveRegion(region->getRegionNumber())) {
520 LDBG() << "Found repetitive region #" << region->getRegionNumber();
521 return region;
522 }
523 } else {
524 LDBG() << "Parent operation does not implement RegionBranchOpInterface";
525 }
526 region = op->getParentRegion();
527 }
528
529 LDBG() << "No enclosing repetitive region found for value";
530 return nullptr;
531}
return success()
static LogicalResult verifyWeights(Operation *op, llvm::ArrayRef< int32_t > weights, std::size_t expectedWeightsNum, llvm::StringRef weightAnchorName, llvm::StringRef weightRefName)
static bool traverseRegionGraph(Region *begin, StopConditionFn stopConditionFn)
Traverse the region graph starting at begin.
static InFlightDiagnostic & printRegionEdgeName(InFlightDiagnostic &diag, RegionBranchPoint sourceNo, RegionSuccessor succRegionNo)
static LogicalResult verifyTypesAlongAllEdges(RegionBranchOpInterface branchOp, RegionBranchPoint sourcePoint, function_ref< FailureOr< TypeRange >(RegionSuccessor)> getInputsTypesForRegion)
Verify that types match along all region control flow edges originating from sourcePoint.
function_ref< bool(Region *, ArrayRef< bool > visited)> StopConditionFn
Stop condition for traverseRegionGraph.
static bool isRegionReachable(Region *begin, Region *r)
Return true if region r is reachable from region begin according to the RegionBranchOpInterface (by t...
b
Return true if permutation is a valid permutation of the outer_dims_perm (case OuterOrInnerPerm::Oute...
static std::string diag(const llvm::Value &value)
Block represents an ordered list of Operations.
Definition Block.h:33
BlockArgument getArgument(unsigned i)
Definition Block.h:129
unsigned getNumArguments()
Definition Block.h:128
This class represents a diagnostic that is inflight and set to be reported.
This class provides a mutable adaptor for a range of operands.
Definition ValueRange.h:118
This class implements the operand iterators for the Operation class.
Definition ValueRange.h:43
unsigned getBeginOperandIndex() const
Return the operand index of the first element of this range.
type_range getTypes() const
Operation is the basic unit of execution within MLIR.
Definition Operation.h:88
unsigned getNumSuccessors()
Definition Operation.h:706
unsigned getNumRegions()
Returns the number of regions held by this operation.
Definition Operation.h:674
InFlightDiagnostic emitError(const Twine &message={})
Emit an error about fatal conditions with this operation, reporting up to any diagnostic handlers tha...
OpTy getParentOfType()
Return the closest surrounding parent operation that is of type 'OpTy'.
Definition Operation.h:238
OperationName getName()
The name of an operation is the key identifier for it.
Definition Operation.h:119
MutableArrayRef< Region > getRegions()
Returns the regions held by this operation.
Definition Operation.h:677
Block * getSuccessor(unsigned index)
Definition Operation.h:708
Region * getParentRegion()
Returns the region to which the instruction belongs.
Definition Operation.h:230
This class represents a point being branched from in the methods of the RegionBranchOpInterface.
Operation * getTerminatorPredecessorOrNull() const
Returns the terminator if branching from a region.
static constexpr RegionBranchPoint parent()
Returns an instance of RegionBranchPoint representing the parent operation.
This class represents a successor of a region.
Region * getSuccessor() const
Return the given region successor.
This class contains a list of basic blocks and a link to the parent operation it is attached to.
Definition Region.h:26
unsigned getRegionNumber()
Return the number of this region in the parent operation.
Definition Region.cpp:62
Operation * getParentOp()
Return the parent operation this region is attached to.
Definition Region.h:200
This class models how operands are forwarded to block arguments in control flow.
SuccessorOperands(MutableOperandRange forwardedOperands)
Constructs a SuccessorOperands with no produced operands that simply forwards operands to the success...
unsigned getProducedOperandCount() const
Returns the amount of operands that are produced internally by the operation.
unsigned size() const
Returns the amount of operands passed to the successor.
OperandRange getForwardedOperands() const
Get the range of operands that are simply forwarded to the successor.
This class provides an abstraction over the various different ranges of value types.
Definition TypeRange.h:37
Instances of the Type class are uniqued, have an immutable identifier and an optional mutable compone...
Definition Types.h:74
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition Value.h:96
Type getType() const
Return the type of this value.
Definition Value.h:105
Region * getParentRegion()
Return the Region in which this Value is defined.
Definition Value.cpp:39
std::optional< BlockArgument > getBranchSuccessorArgument(const SuccessorOperands &operands, unsigned operandIndex, Block *successor)
Return the BlockArgument corresponding to operand operandIndex in some successor if operandIndex is w...
LogicalResult verifyRegionBranchWeights(Operation *op)
Verify that the region weights attached to an operation implementing WeightedRegiobBranchOpInterface ...
LogicalResult verifyBranchSuccessorOperands(Operation *op, unsigned succNo, const SuccessorOperands &operands)
Verify that the given operands match those of the given successor block.
LogicalResult verifyTypesAlongControlFlowEdges(Operation *op)
Verify that types match along control flow edges described the given op.
LogicalResult verifyBranchWeights(Operation *op)
Verify that the branch weights attached to an operation implementing WeightedBranchOpInterface are co...
Include the generated interface declarations.
bool insideMutuallyExclusiveRegions(Operation *a, Operation *b)
Return true if a and b are in mutually exclusive regions as per RegionBranchOpInterface.
Region * getEnclosingRepetitiveRegion(Operation *op)
Return the first enclosing region of the given op that may be executed repetitively as per RegionBran...
llvm::function_ref< Fn > function_ref
Definition LLVM.h:152