@@ -11,14 +11,11 @@
1111#include "mlir/Dialect/Arithmetic/IR/Arithmetic.h"
1212#include "mlir/Dialect/Func/IR/FuncOps.h"
1313#include "mlir/Dialect/GPU/IR/GPUDialect.h"
14-#include "mlir/Dialect/Linalg/Utils/Utils.h"
1514#include "mlir/Dialect/MemRef/IR/MemRef.h"
16-#include "mlir/Dialect/PDL/IR/PDLOps.h"
1715#include "mlir/Dialect/SCF/IR/SCF.h"
1816#include "mlir/Dialect/Vector/IR/VectorOps.h"
1917#include "mlir/Dialect/Vector/Transforms/VectorDistribution.h"
2018#include "mlir/Dialect/Vector/Transforms/VectorRewritePatterns.h"
21-#include "mlir/IR/OpImplementation.h"
2219#include "mlir/IR/Region.h"
2320#include "mlir/Transforms/GreedyPatternRewriteDriver.h"
2421@@ -40,20 +37,20 @@ void mlir::iree_compiler::registerTransformDialectLLVMGPUExtension(
4037// TODO: Maybe we need both a transform.iree.cpu.bufferize and a
4138// transform.iree.gpu.bufferize rather than a single common bufferize op?
423943-/// Apply the permutation `perm` to `vals.
44-/// Return failure if perm is not a permutation.
40+/// Apply the permutation `perm` to `vals; i.e. vals[i] is stored into
41+/// res[perm[i]] Return failure if perm is not a permutation.
4542// TODO: upstream as extraClassDeclaration once stabilized.
4643template <typename T>
4744static FailureOr<SmallVector<T>> permute(const SmallVector<T> &vals,
4845 ArrayRef<int64_t> perm) {
4946if (vals.size() != perm.size()) return failure();
5047 SmallVector<T> result(vals.size());
5148 SmallVector<bool> seen(vals.size());
52-for (const auto &it : llvm::zip(perm, vals)) {
49+for (auto [idx, val] : llvm::zip(perm, vals)) {
5350// Already seen, invalid thread_dim_mapping.
54-if (seen[std::get<0>(it)]) return failure();
55- result[std::get<0>(it)] = std::get<1>(it);
56- seen[std::get<0>(it)] = true;
51+if (seen[idx]) return failure();
52+ result[idx] = val;
53+ seen[idx] = true;
5754 }
5855// Some not seen, invalid thread_dim_mapping.
5956if (!llvm::all_of(seen, [](bool b) { return b; })) return failure();
@@ -64,7 +61,7 @@ static FailureOr<SmallVector<T>> permute(const SmallVector<T> &vals,
6461/// `foreachThreadOp` to `values`.
6562// TODO: upstream as extraClassDeclaration once stabilized.
6663template <typename T>
67-static FailureOr<SmallVector<T>> getPermuted(
64+static FailureOr<SmallVector<T>> getValuesPermutedByThreadMapping(
6865 scf::ForeachThreadOp foreachThreadOp, const SmallVector<T> &values) {
6966// Apply mapping permutation if specified.
7067auto mapping = foreachThreadOp.getThreadDimMapping();
@@ -84,7 +81,7 @@ static FailureOr<SmallVector<OpFoldResult>> getNumThreads(
8481 OpBuilder &b, scf::ForeachThreadOp foreachThreadOp) {
8582 SmallVector<OpFoldResult> threadCount = foreachThreadOp.getNumThreads();
8683 threadCount.resize(3, b.getIndexAttr(1));
87-return getPermuted(foreachThreadOp, threadCount);
84+return getValuesPermutedByThreadMapping(foreachThreadOp, threadCount);
8885}
89869087/// Helper to get the thread indices of a `foreachThreadOp` after applying the
@@ -94,7 +91,7 @@ static FailureOr<SmallVector<Value>> getThreadIndices(
9491 OpBuilder &b, scf::ForeachThreadOp foreachThreadOp) {
9592 SmallVector<Value> threadCount = foreachThreadOp.getThreadIndices();
9693 threadCount.resize(3, Value());
97-return getPermuted(foreachThreadOp, threadCount);
94+return getValuesPermutedByThreadMapping(foreachThreadOp, threadCount);
9895}
999610097//===---------------------------------------------------------------------===//
@@ -120,9 +117,9 @@ mlir::iree_compiler::rewriteForeachThreadToGpu(
120117 }))
121118return foreachThreadOp->emitError("unsupported dynamic workgroup size");
122119123- SmallVector<int64_t> workgroupSizes;
124-for (OpFoldResult ofr : *maybeWorkgroupSizes)
125-workgroupSizes.push_back(getConstantIntValue(ofr).value());
120+ SmallVector<int64_t> workgroupSizes = llvm::to_vector(llvm::map_range(
121+ *maybeWorkgroupSizes,
122+ [](OpFoldResult ofr) { return getConstantIntValue(ofr).value(); }));
126123127124// Step 1. Create the gpu.thread ops
128125 Location loc = foreachThreadOp.getLoc();
@@ -138,11 +135,12 @@ mlir::iree_compiler::rewriteForeachThreadToGpu(
138135139136// Step 2. Maybe create conditionals to predicate the region.
140137 Value predicate;
141-for (auto it : llvm::zip(threadOps, workgroupSizes, globalWorkgroupSizes)) {
142-auto threadId = std::get<0>(it);
143-auto workgroupSize = std::get<1>(it);
144-auto globalWorkgroupSize = std::get<2>(it);
145-assert(workgroupSize <= globalWorkgroupSize && "workgroup size overflow");
138+for (auto [threadId, workgroupSize, globalWorkgroupSize] :
139+llvm::zip(threadOps, workgroupSizes, globalWorkgroupSizes)) {
140+if (workgroupSize > globalWorkgroupSize) {
141+return foreachThreadOp.emitOpError("workgroup size overflow: ")
142+<< workgroupSize << " > " << globalWorkgroupSize;
143+ }
146144if (workgroupSize == globalWorkgroupSize) continue;
147145 Value tmpPredicate = rewriter.create<arith::CmpIOp>(
148146 loc, arith::CmpIPredicate::ult, threadId,
@@ -220,6 +218,7 @@ transform_dialect::ForeachThreadToGpuAndTranslationInfo::applyToOne(
220218221219 SmallVector<int64_t> workgroupSize =
222220extractFromI64ArrayAttr(getWorkgroupSize());
221+// TODO: no magic constant but IREE uses this extensively.
223222 workgroupSize.resize(/*size=*/3, /*value=*/1);
224223 SimplePatternRewriter rewriter(target);
225224auto walkResult = target->walk([&](scf::ForeachThreadOp foreachThreadOp) {
@@ -242,13 +241,39 @@ transform_dialect::ForeachThreadToGpuAndTranslationInfo::applyToOne(
242241}
243242244243//===---------------------------------------------------------------------===//
245-// VectorWarpExecuteOnLane0Op.
244+// VectorToWarpExecuteOnLane0Op.
246245//===---------------------------------------------------------------------===//
247246247+/// Helper method to replace all uses of the laneId operand by the constant
248+/// 0 inside the region. This is a necessary prerequisite to perform any kind of
249+/// hoisting of IR that is inside the region.
250+/// Return success if any replacement occurred, failure otherwise.
251+// TODO: this is currently brittle, what we really need here is a scope-aware
252+// SCCP.
253+static LogicalResult replaceAllUsesOfLaneWithin(
254+ RewriterBase &b, vector::WarpExecuteOnLane0Op executeOp) {
255+ OpBuilder::InsertionGuard g(b);
256+ b.setInsertionPoint(executeOp);
257+ Value zero = b.create<arith::ConstantIndexOp>(executeOp.getLoc(), 0);
258+ b.setInsertionPointToStart(&executeOp.getWarpRegion().front());
259+ Value laneId = executeOp.getLaneid();
260+bool applied = false;
261+for (Operation *user : llvm::make_early_inc_range(laneId.getUsers())) {
262+if (!executeOp->isProperAncestor(user)) continue;
263+ b.startRootUpdate(user);
264+ user->replaceUsesOfWith(laneId, zero);
265+ b.finalizeRootUpdate(user);
266+ applied = true;
267+ }
268+return success(applied);
269+}
270+271+/// Return the gpu::ThreadIdOp for which the predicate if equivalent to
272+/// `if (threadIdx.x == 0)`.
248273// TODO: Figure out the proper canonicalization and drop the complexity here.
249274// TODO: More sophisticated detection for matching
250275// (threadIdx.x == 0 && other stuff not involving threadIdx.x)
251-static LogicalResult isThreadIdxxZeroPredicate(scf::IfOp ifOp) {
276+static FailureOr<gpu::ThreadIdOp> isThreadIdxxZeroPredicate(scf::IfOp ifOp) {
252277if (!ifOp || ifOp.getNumResults() > 0 ||
253278 ifOp.getThenRegion().getBlocks().size() != 1 ||
254279 !ifOp.getElseRegion().empty())
@@ -261,32 +286,34 @@ static LogicalResult isThreadIdxxZeroPredicate(scf::IfOp ifOp) {
261286auto ULT = arith::CmpIPredicate::ult;
262287auto ULE = arith::CmpIPredicate::ule;
263288if (auto threadIdOp = pred.getLhs().getDefiningOp<gpu::ThreadIdOp>()) {
289+if (threadIdOp.dimension() != gpu::Dimension::x) return failure();
264290if (pred.getPredicate() == EQ && isConstantIntValue(pred.getRhs(), 0))
265-return success();
291+return threadIdOp;
266292if (pred.getPredicate() == SLE && isConstantIntValue(pred.getRhs(), 0))
267-return success();
293+return threadIdOp;
268294if (pred.getPredicate() == ULE && isConstantIntValue(pred.getRhs(), 0))
269-return success();
295+return threadIdOp;
270296if (pred.getPredicate() == SLT && isConstantIntValue(pred.getRhs(), 1))
271-return success();
297+return threadIdOp;
272298if (pred.getPredicate() == ULT && isConstantIntValue(pred.getRhs(), 1))
273-return success();
299+return threadIdOp;
274300 }
275301auto SGT = arith::CmpIPredicate::sgt;
276302auto SGE = arith::CmpIPredicate::sge;
277303auto UGT = arith::CmpIPredicate::ugt;
278304auto UGE = arith::CmpIPredicate::uge;
279305if (auto threadIdOp = pred.getRhs().getDefiningOp<gpu::ThreadIdOp>()) {
306+if (threadIdOp.dimension() != gpu::Dimension::x) return failure();
280307if (pred.getPredicate() == EQ && isConstantIntValue(pred.getLhs(), 0))
281-return success();
308+return threadIdOp;
282309if (pred.getPredicate() == SGE && isConstantIntValue(pred.getLhs(), 0))
283-return success();
310+return threadIdOp;
284311if (pred.getPredicate() == UGE && isConstantIntValue(pred.getLhs(), 0))
285-return success();
312+return threadIdOp;
286313if (pred.getPredicate() == SGT && isConstantIntValue(pred.getLhs(), 1))
287-return success();
314+return threadIdOp;
288315if (pred.getPredicate() == UGT && isConstantIntValue(pred.getLhs(), 1))
289-return success();
316+return threadIdOp;
290317 }
291318return failure();
292319}
@@ -295,16 +322,19 @@ struct VectorDistributionResult {
295322 vector::WarpExecuteOnLane0Op warpOp;
296323};
297324298-static FailureOr<VectorDistributionResult> vectorDistribution(
325+static FailureOr<VectorDistributionResult> rewriteScfIfAsWarpExecuteOnLane0(
299326 PatternRewriter &rewriter, Location loc, scf::IfOp ifOp,
300327int64_t workgroupSizeX, int64_t warpSize) {
301328// Bail if cond is not `if (threadIdx.x == 0)`.
302-if (failed(isThreadIdxxZeroPredicate(ifOp))) return failure();
329+ FailureOr<gpu::ThreadIdOp> maybeThreadIdxxOp =
330+isThreadIdxxZeroPredicate(ifOp);
331+if (failed(maybeThreadIdxxOp)) return failure();
303332304333// All the code below will be executed on a single warp given a fixed
305334// (threadIdxy, threadIdxz).
306- Value threadIdxx = rewriter.create<gpu::ThreadIdOp>(
307- loc, rewriter.getIndexType(), gpu::Dimension::x);
335+// Note, we reuse `maybeThreadIdxxOp` here because we later want to replace
336+// this op instance by 0 without relying on CSE or canonicalizations.
337+ Value threadIdxx = *maybeThreadIdxxOp;
308338309339assert(workgroupSizeX % warpSize == 0);
310340if (workgroupSizeX != warpSize) {
@@ -334,6 +364,13 @@ static FailureOr<VectorDistributionResult> vectorDistribution(
334364// Erase old op.
335365 rewriter.eraseOp(ifOp);
336366367+// This simple rewrite propagates zero in lieu of laneId within the
368+// warp_execute_on_lane_0 op.
369+// Atm, this **must** occur before any hoisting of code.
370+// TODO: Replace this by a more robust scoped SCCP that will make it more
371+// robust re. hoisting.
372+ (void)replaceAllUsesOfLaneWithin(rewriter, warpOp);
373+337374// Hoist the scalar code outside of the warp region.
338375// Note: moving code does not require a listener.
339376vector::moveScalarUniformCode(warpOp);
@@ -355,15 +392,15 @@ static HAL::ExecutableExportOp getExecutableExportOpForFunc(
355392}
356393357394DiagnosedSilenceableFailure
358-transform_dialect::VectorWarpExecuteOnLane0Op::applyToOne(
395+transform_dialect::VectorToWarpExecuteOnLane0Op::applyToOne(
359396 scf::IfOp target, SmallVectorImpl<Operation *> &results,
360397 transform::TransformState &state) {
361398if (!isa<HAL::ExecutableOp, HAL::ExecutableVariantOp>(state.getTopLevel())) {
362399 state.getTopLevel()->emitOpError(
363400"requires HAL::ExecutableOp or HAL::ExecutableVariantOp toplevel so "
364-"that "
365-"IR is properly isolated. This is required so we can safely inspect "
366-"the HAL::ExecutableExportOp under multi-threaded pass assumptions.");
401+"that IR is properly isolated. This is required so we can safely "
402+"inspect the HAL::ExecutableExportOp under multi-threaded pass "
403+"assumptions.");
367404return DiagnosedSilenceableFailure(reportUnknownTransformError(target));
368405 }
369406@@ -402,8 +439,8 @@ transform_dialect::VectorWarpExecuteOnLane0Op::applyToOne(
402439403440 SimplePatternRewriter rewriter(target);
404441 FailureOr<VectorDistributionResult> vectorDistributionResult =
405-vectorDistribution(rewriter, target->getLoc(), target, workgroupSizeX,
406- warpSize);
442+rewriteScfIfAsWarpExecuteOnLane0(rewriter, target->getLoc(), target,
443+ workgroupSizeX, warpSize);
407444if (failed(vectorDistributionResult)) {
408445// Return a silenceable failure and set the expected 1 result to nullptr.
409446 results.assign(1, nullptr);
@@ -477,6 +514,7 @@ static LogicalResult applyMultiReductionLoweringPatterns(Operation *target) {
477514478515 MLIRContext *ctx = target->getContext();
479516 RewritePatternSet patterns(ctx);
517+480518vector::populateVectorMultiReductionLoweringPatterns(
481519 patterns, vector::VectorMultiReductionLowering::InnerReduction);
482520 patterns.add<InsertElementToBroadcast>(ctx);