Skip to content

Commit 3dfea72

Browse files
committed
[mlir] relax transform dialect multi-handle restriction
Relax the restriction in the transform dialect interpreter utilities that expected a payload IR op to be assocaited with at most one transform IR handle value. This was useful during the initial bootstrapping to avoid use-after-free error equivalents when a payload IR op could be erased through one of the handles associated with it and then accessed through another. It was, however, possible to erase an ancestor of the payload IR operation in question. The expensive-checks mode of interpretation is able to detect both cases and has proven sufficiently robust in debugging use-after-free errors. Reviewed By: springerm Differential Revision: https://reviews.llvm.org/D134964
1 parent 89bb0ca commit 3dfea72

File tree

8 files changed

+240
-180
lines changed

8 files changed

+240
-180
lines changed

mlir/include/mlir/Dialect/Transform/IR/TransformDialect.td

Lines changed: 28 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ def Transform_Dialect : Dialect {
6666

6767
A Transform IR value such as `%0` may be associated with multiple payload
6868
operations. This is conceptually a set of operations and no assumptions
69-
should be made about the order of ops. Most Transform IR ops support
70-
operand values that are mapped to multiple operations. They usually apply
71-
the respective transformation for every mapped op ("batched execution").
72-
Deviations from this convention are described in the documentation of
73-
Transform IR ops.
69+
should be made about the order of ops unless specified otherwise by the
70+
operation. Most Transform IR ops support operand values that are mapped to
71+
multiple operations. They usually apply the respective transformation for
72+
every mapped op ("batched execution"). Deviations from this convention are
73+
described in the documentation of Transform IR ops.
7474

7575
Overall, Transform IR ops are expected to be contained in a single top-level
7676
op. Such top-level ops specify how to apply the transformations described
@@ -161,17 +161,18 @@ def Transform_Dialect : Dialect {
161161

162162
## Execution Model
163163

164-
The transformation starts at the specifed top-level transform IR operation
165-
and applies to some payload IR scope, identified by the payload IR op that
166-
contains the IR to transform. It is the responsibility of the user to
167-
properly select the scope and/or to avoid the transformations to modify the
168-
IR outside of the given scope. The top-level transform IR operation may
169-
contain further transform operations and execute them in the desired order.
164+
The transformation starts at the user-specified top-level transform IR
165+
operation and applies to some user-specified payload IR scope, identified by
166+
the payload IR op that contains the IR to transform. It is the
167+
responsibility of the user to properly select the scope and/or to avoid the
168+
transformations to modify the IR outside of the given scope. The top-level
169+
transform IR operation may contain further transform operations and execute
170+
them in the desired order.
170171

171172
Transformation application functions produce a tri-state status:
172173

173174
- success;
174-
- recoverable (silencable) failure;
175+
- recoverable (silenceable) failure;
175176
- irrecoverable failure.
176177

177178
Transformation container operations may intercept recoverable failures and
@@ -180,9 +181,9 @@ def Transform_Dialect : Dialect {
180181
failures, the diagnostics are emitted immediately whereas their emission is
181182
postponed for recoverable failures. Transformation container operations may
182183
also fail to recover from a theoretically recoverable failure, in which case
183-
they are expected to emit the diagnostic and turn the failure into an
184-
irrecoverable one. A recoverable failure produced by applying the top-level
185-
transform IR operation is considered irrecoverable.
184+
they can either propagate it to their parent or emit the diagnostic and turn
185+
the failure into an irrecoverable one. A recoverable failure produced by
186+
applying the top-level transform IR operation is considered irrecoverable.
186187

187188
Transformation container operations are allowed to "step over" some nested
188189
operations if the application of some previous operation produced a failure.
@@ -193,26 +194,18 @@ def Transform_Dialect : Dialect {
193194

194195
## Handle Invalidation
195196

196-
The execution model of the transform dialect expects that a payload IR
197-
operation is associated with _at most one_ transform IR handle. This avoids
198-
the situation when a handle to an operation outlives the operation itself
199-
that can be erased during a transformation triggered through another handle.
200-
201-
Handles pointing to operations nested in each other are allowed to co-exist
202-
in the transform IR. However, a transform IR operation that consumes such a
203-
handle automatically _invalidates_ all the other handles that are associated
204-
with operations nested in the operations associated with the consumed
205-
handle. Any use of the invalidated handle results in undefined behavior
206-
since the payload IR operations associated with it are likely to have been
207-
mutated or erased. The mere fact of the handle being invalidated does _not_
208-
trigger undefined behavior, only its appearance as an operand does.
209-
Invalidation applies to the entire handle, even if some of the payload IR
210-
operations associated with it are not nested in payload IR operations
211-
associated with another, consumed handle.
212-
213-
Note: the restriction on two handles not pointing to the same operation may
214-
be relaxed in the future to follow the invalidation model for nested
215-
operation.
197+
The execution model of the transform dialect allows a payload IR operation
198+
to be associated with _multiple_ handles as well as nested payload IR
199+
operations to be associated with different handles. A transform IR operation
200+
that consumes a handle automatically _invalidates_ all the other handles
201+
associated with the same payload IR operations, or with any of their
202+
descendants, as the consumed handle. Note that the _entire_ handle is
203+
invalidated, even if some of the payload IR operations associated with it
204+
or their ancestors were not associated with the consumed handle. Any use of
205+
the invalidated handle results in undefined behavior since the payload IR
206+
operations associated with it are likely to have been mutated or erased. The
207+
mere fact of the handle being invalidated does _not_ trigger undefined
208+
behavior, only its appearance as an operand does.
216209

217210
The Transform dialect infrastructure has the capability of checking whether
218211
the transform IR op operand is invalidated before applying the

mlir/include/mlir/Dialect/Transform/IR/TransformInterfaces.h

Lines changed: 53 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,13 @@ class TransformOptions {
209209
/// TransformOpInterface. The operations implementing this interface and the
210210
/// surrounding structure are referred to as transform IR. The operations to
211211
/// which transformations apply are referred to as payload IR. The state thus
212-
/// contains the mapping between values defined in the transform IR ops and
213-
/// payload IR ops. It assumes that each value in the transform IR can be used
214-
/// at most once (since transformations are likely to change the payload IR ops
215-
/// the value corresponds to). Checks that transform IR values correspond to
216-
/// disjoint sets of payload IR ops throughout the transformation.
212+
/// contains the many-to-many mapping between values defined in the transform IR
213+
/// ops and payload IR ops. The "expensive-checks" option can be passed to
214+
/// the constructor at transformation execution time that transform IR values
215+
/// used as operands by a transform IR operation are not associated with
216+
/// dangling pointers to payload IR operations that are known to have been
217+
/// erased by previous transformation through the same or a different transform
218+
/// IR value.
217219
///
218220
/// A reference to this class is passed as an argument to "apply" methods of the
219221
/// transform op interface. Thus the "apply" method can call
@@ -235,9 +237,10 @@ class TransformState {
235237
/// operations in the payload IR.
236238
using TransformOpMapping = DenseMap<Value, SmallVector<Operation *>>;
237239

238-
/// Mapping between a payload IR operation and the transform IR value it is
239-
/// currently associated with.
240-
using TransformOpReverseMapping = DenseMap<Operation *, Value>;
240+
/// Mapping between a payload IR operation and the transform IR values it is
241+
/// associated with.
242+
using TransformOpReverseMapping =
243+
DenseMap<Operation *, SmallVector<Value, 2>>;
241244

242245
/// Bidirectional mappings between transform IR values and payload IR
243246
/// operations.
@@ -249,7 +252,7 @@ class TransformState {
249252
public:
250253
/// Creates a state for transform ops living in the given region. The parent
251254
/// operation of the region. The second argument points to the root operation
252-
/// in the payload IR beind transformed, which may or may not contain the
255+
/// in the payload IR being transformed, which may or may not contain the
253256
/// region with transform ops. Additional options can be provided through the
254257
/// trailing configuration object.
255258
TransformState(Region &region, Operation *root,
@@ -263,9 +266,10 @@ class TransformState {
263266
/// This is helpful for transformations that apply to a particular handle.
264267
ArrayRef<Operation *> getPayloadOps(Value value) const;
265268

266-
/// Returns the Transform IR handle for the given Payload IR op if it exists
267-
/// in the state, null otherwise.
268-
Value getHandleForPayloadOp(Operation *op) const;
269+
/// Populates `handles` with all handles pointing to the given Payload IR op.
270+
/// Returns success if such handles exist, failure otherwise.
271+
LogicalResult getHandlesForPayloadOp(Operation *op,
272+
SmallVectorImpl<Value> &handles) const;
269273

270274
/// Applies the transformation specified by the given transform op and updates
271275
/// the state accordingly.
@@ -275,13 +279,13 @@ class TransformState {
275279
/// list of operations in the payload IR. The arguments must be defined in
276280
/// blocks of the currently processed transform IR region, typically after a
277281
/// region scope is defined.
278-
LogicalResult mapBlockArguments(BlockArgument argument,
279-
ArrayRef<Operation *> operations) {
282+
void mapBlockArguments(BlockArgument argument,
283+
ArrayRef<Operation *> operations) {
280284
#if LLVM_ENABLE_ABI_BREAKING_CHECKS
281285
assert(argument.getParentRegion() == regionStack.back() &&
282286
"mapping block arguments from a region other than the active one");
283287
#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
284-
return setPayloadOps(argument, operations);
288+
setPayloadOps(argument, operations);
285289
}
286290

287291
// Forward declarations to support limited visibility.
@@ -379,7 +383,8 @@ class TransformState {
379383
const TransformState &getTransformState() const { return state; }
380384

381385
/// Replaces the given payload op with another op. If the replacement op is
382-
/// null, removes the association of the payload op with its handle.
386+
/// null, removes the association of the payload op with its handle. Returns
387+
/// failure if the op is not associated with any handle.
383388
LogicalResult replacePayloadOp(Operation *op, Operation *replacement);
384389

385390
private:
@@ -451,20 +456,29 @@ class TransformState {
451456
return it->second;
452457
}
453458

454-
/// Sets the payload IR ops associated with the given transform IR value.
455-
/// Fails if this would result in multiple transform IR values with uses
456-
/// corresponding to the same payload IR ops. For example, a hypothetical
457-
/// "find function by name" transform op would (indirectly) call this
458-
/// function for its result. Having two such calls in a row with for different
459-
/// values, e.g. coming from different ops:
459+
/// Removes the mapping between the given payload IR operation and the given
460+
/// transform IR value.
461+
void dropReverseMapping(Mappings &mappings, Operation *op, Value value);
462+
463+
/// Sets the payload IR ops associated with the given transform IR value
464+
/// (handle). A payload op may be associated multiple handles as long as
465+
/// at most one of them gets consumed by further transformations.
466+
/// For example, a hypothetical "find function by name" may be called twice in
467+
/// a row to produce two handles pointing to the same function:
460468
///
461469
/// %0 = transform.find_func_by_name { name = "myfunc" }
462470
/// %1 = transform.find_func_by_name { name = "myfunc" }
463471
///
464-
/// would lead to both values pointing to the same operation. The second call
465-
/// to setPayloadOps will fail, unless the association with the %0 value is
466-
/// removed first by calling update/removePayloadOps.
467-
LogicalResult setPayloadOps(Value value, ArrayRef<Operation *> targets);
472+
/// which is valid by itself. However, calling a hypothetical "rewrite and
473+
/// rename function" transform on both handles:
474+
///
475+
/// transform.rewrite_and_rename %0 { new_name = "func" }
476+
/// transform.rewrite_and_rename %1 { new_name = "func" }
477+
///
478+
/// is invalid given the transformation "consumes" the handle as expressed
479+
/// by side effects. Practically, a transformation consuming a handle means
480+
/// that the associated payload operation may no longer exist.
481+
void setPayloadOps(Value value, ArrayRef<Operation *> targets);
468482

469483
/// Forgets the payload IR ops associated with the given transform IR value.
470484
void removePayloadOps(Value value);
@@ -473,24 +487,18 @@ class TransformState {
473487
/// The callback function is called once per associated operation and is
474488
/// expected to return the modified operation or nullptr. In the latter case,
475489
/// the corresponding operation is no longer associated with the transform IR
476-
/// value. May fail if the operation produced by the update callback is
477-
/// already associated with a different Transform IR handle value.
478-
LogicalResult
479-
updatePayloadOps(Value value,
480-
function_ref<Operation *(Operation *)> callback);
481-
482-
/// Attempts to record the mapping between the given Payload IR operation and
483-
/// the given Transform IR handle. Fails and reports an error if the operation
484-
/// is already tracked by another handle.
485-
static LogicalResult tryEmplaceReverseMapping(Mappings &map, Operation *op,
486-
Value handle);
490+
/// value.
491+
void updatePayloadOps(Value value,
492+
function_ref<Operation *(Operation *)> callback);
487493

488494
/// If the operand is a handle consumed by the operation, i.e. has the "free"
489495
/// memory effect associated with it, identifies other handles that are
490496
/// pointing to payload IR operations nested in the operations pointed to by
491-
/// the consumed handle. Marks all such handles as invalidated so trigger
497+
/// the consumed handle. Marks all such handles as invalidated to trigger
492498
/// errors if they are used.
493499
void recordHandleInvalidation(OpOperand &handle);
500+
void recordHandleInvalidationOne(OpOperand &handle, Operation *payloadOp,
501+
Value otherHandle);
494502

495503
/// Checks that the operation does not use invalidated handles as operands.
496504
/// Reports errors and returns failure if it does. Otherwise, invalidates the
@@ -566,9 +574,9 @@ namespace detail {
566574
/// Maps the only block argument of the op with PossibleTopLevelTransformOpTrait
567575
/// to either the list of operations associated with its operand or the root of
568576
/// the payload IR, depending on what is available in the context.
569-
LogicalResult
570-
mapPossibleTopLevelTransformOpBlockArguments(TransformState &state,
571-
Operation *op, Region &region);
577+
void mapPossibleTopLevelTransformOpBlockArguments(TransformState &state,
578+
Operation *op,
579+
Region &region);
572580

573581
/// Verification hook for PossibleTopLevelTransformOpTrait.
574582
LogicalResult verifyPossibleTopLevelTransformOpTrait(Operation *op);
@@ -605,18 +613,17 @@ class PossibleTopLevelTransformOpTrait
605613
/// Sets up the mapping between the entry block of the given region of this op
606614
/// and the relevant list of Payload IR operations in the given state. The
607615
/// state is expected to be already scoped at the region of this operation.
608-
/// Returns failure if the mapping failed, e.g., the value is already mapped.
609-
LogicalResult mapBlockArguments(TransformState &state, Region &region) {
616+
void mapBlockArguments(TransformState &state, Region &region) {
610617
assert(region.getParentOp() == this->getOperation() &&
611618
"op comes from the wrong region");
612-
return detail::mapPossibleTopLevelTransformOpBlockArguments(
619+
detail::mapPossibleTopLevelTransformOpBlockArguments(
613620
state, this->getOperation(), region);
614621
}
615-
LogicalResult mapBlockArguments(TransformState &state) {
622+
void mapBlockArguments(TransformState &state) {
616623
assert(
617624
this->getOperation()->getNumRegions() == 1 &&
618625
"must indicate the region to map if the operation has more than one");
619-
return mapBlockArguments(state, this->getOperation()->getRegion(0));
626+
mapBlockArguments(state, this->getOperation()->getRegion(0));
620627
}
621628
};
622629

0 commit comments

Comments
 (0)