Skip to content

Commit dda2755

Browse files
authored
[aievec] Lowers divf to peano intrinsic (#2851)
1 parent e1d19f9 commit dda2755

6 files changed

Lines changed: 328 additions & 4 deletions

File tree

include/aie/Dialect/AIEVec/IR/AIEVecOps.td

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,25 @@ def AIEVec_ExpOp:
516516
let hasVerifier = 0;
517517
}
518518

519+
def AIEVec_InvOp:
520+
AIEVec_Op<"inv", [
521+
Pure,
522+
AllTypesMatch<["source", "result"]>
523+
]>,
524+
Arguments<(ins AnyTypeOf<[F32, VectorOfLengthAndType<[16, 32], [F32]>]>:$source)>,
525+
Results<(outs AnyTypeOf<[F32, VectorOfLengthAndType<[16, 32], [F32]>]>:$result)> {
526+
let summary = "AIE inverse";
527+
let description = [{
528+
AMD-specific intrinsic that computes the inverse (1/x) of a scalar f32
529+
or a vector of f32 elements.
530+
For AIE2P, scalar f32 is lowered to the inv intrinsic, and vector f32
531+
is unrolled into scalar inv intrinsic calls.
532+
`$result = inv(`$source`) = 1.0 / $source`.
533+
}];
534+
let assemblyFormat = "$source attr-dict `:` type($result)";
535+
let hasVerifier = 0;
536+
}
537+
519538
def AIEVec_BxorOp:
520539
AIEVec_Op<"bxor", [
521540
Pure,

include/aie/Dialect/XLLVM/IR/XLLVMAIE2IntrOps.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,13 @@ def InvsqrtAIE2pIntrOp :
626626
[Pure, TypeIs<"res", F32>]>,
627627
Arguments<(ins F32:$src)>;
628628

629+
// ----- INV (Reciprocal) -----
630+
631+
def InvAIE2pIntrOp :
632+
AIEVec2p_IntrOp<"inv",
633+
[Pure, TypeIs<"res", F32>]>,
634+
Arguments<(ins F32:$src)>;
635+
629636
// ----- SHIFT -----
630637

631638
def VectorShiftI512I512IntrOp :

lib/Conversion/AIEVecToLLVM/AIEVecToLLVM.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4598,6 +4598,58 @@ class ShuffleOpConversion
45984598
}
45994599
};
46004600

4601+
// Convert aievec.inv to xllvm.intr.aie2p.inv intrinsic for AIE2P
4602+
// Scalar f32: direct conversion to xllvm.intr.aie2p.inv
4603+
// Vector f32: unroll into scalar xllvm.intr.aie2p.inv operations
4604+
class InvOpAIE2pConversion
4605+
: public mlir::ConvertOpToLLVMPattern<aievec::InvOp> {
4606+
public:
4607+
using ConvertOpToLLVMPattern<aievec::InvOp>::ConvertOpToLLVMPattern;
4608+
4609+
LogicalResult
4610+
matchAndRewrite(aievec::InvOp invOp, OpAdaptor adaptor,
4611+
ConversionPatternRewriter &rewriter) const override {
4612+
auto loc = invOp.getLoc();
4613+
auto operandType = adaptor.getSource().getType();
4614+
4615+
// Handle scalar f32 inverse
4616+
if (operandType.isF32()) {
4617+
auto invResult = xllvm::InvAIE2pIntrOp::create(
4618+
rewriter, loc, rewriter.getF32Type(), adaptor.getSource());
4619+
rewriter.replaceOp(invOp, invResult);
4620+
return success();
4621+
}
4622+
4623+
// Handle vector<N x f32> inverse
4624+
auto vecType = dyn_cast<VectorType>(operandType);
4625+
if (!vecType || !vecType.getElementType().isF32())
4626+
return failure();
4627+
4628+
// Unroll vector inverse into scalar operations
4629+
int numElements = getVectorLaneSize(vecType);
4630+
Value result = LLVM::PoisonOp::create(rewriter, loc, vecType);
4631+
4632+
for (int i = 0; i < numElements; ++i) {
4633+
// Extract element i
4634+
auto indexCst = LLVM::ConstantOp::create(
4635+
rewriter, loc, rewriter.getI64Type(), rewriter.getI64IntegerAttr(i));
4636+
auto extractedElem = LLVM::ExtractElementOp::create(
4637+
rewriter, loc, adaptor.getSource(), indexCst);
4638+
4639+
// Call xllvm.intr.aie2p.inv on the scalar
4640+
auto invResult = xllvm::InvAIE2pIntrOp::create(
4641+
rewriter, loc, rewriter.getF32Type(), extractedElem);
4642+
4643+
// Insert result back into vector
4644+
result = LLVM::InsertElementOp::create(rewriter, loc, vecType, result,
4645+
invResult, indexCst);
4646+
}
4647+
4648+
rewriter.replaceOp(invOp, result);
4649+
return success();
4650+
}
4651+
};
4652+
46014653
// Convert aievec.exp to xllvm.exp2 intrinsic for AIE2P
46024654
// Uses the identity: exp(x) = exp2(x * log2(e))
46034655
// Supports both lane-16 and lane-32 bf16 vectors
@@ -4941,6 +4993,7 @@ void populateAIEVecToLLVMAIE2pConversionPatterns(
49414993
patterns.add<ExtractElemOpAIE2pConversion>(converter);
49424994
patterns.add<ConcatOpAIE2pConversion>(converter);
49434995
patterns.add<ExpOpAIE2pConversion>(converter);
4996+
patterns.add<InvOpAIE2pConversion>(converter);
49444997
patterns.add<BroadcastScalarOpAIE2pConversion>(converter);
49454998
patterns.add<RsqrtOpAIE2pConversion>(converter);
49464999
patterns.add<FdivOpAIE2pConversion>(converter);

lib/Dialect/AIEVec/Transforms/VectorToAIEVecConversions.cpp

Lines changed: 111 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2372,7 +2372,7 @@ struct ComputeExpOpByLUTPattern : OpConversionPattern<math::ExpOp> {
23722372
}
23732373
};
23742374

2375-
// Lower the inverse of a float to a function call
2375+
// Lower the inverse of a float to a function call (CPP backend)
23762376
// Convert the pattern-
23772377
// %cst = arith.constant 1.000000e+00 : f32
23782378
// %0 = arith.divf %cst, %arg1 : f32
@@ -2426,6 +2426,77 @@ struct ComputeInvOpByLUTPattern : OpConversionPattern<arith::DivFOp> {
24262426
}
24272427
};
24282428

2429+
// Lower the inverse of a float to aievec.inv (LLVMIR backend for AIE2P)
2430+
// Supports both scalar f32 and vector<Nxf32> types.
2431+
// Convert the pattern-
2432+
// %cst = arith.constant 1.000000e+00 : f32
2433+
// %0 = arith.divf %cst, %arg1 : f32
2434+
// to -
2435+
// %0 = aievec.inv %arg1 : f32
2436+
// Also supports:
2437+
// %cst = arith.constant dense<1.0> : vector<16xf32>
2438+
// %0 = arith.divf %cst, %arg1 : vector<16xf32>
2439+
// to -
2440+
// %0 = aievec.inv %arg1 : vector<16xf32>
2441+
struct ConvertDivFToAIEVecInvOpPattern : OpConversionPattern<arith::DivFOp> {
2442+
using OpConversionPattern::OpConversionPattern;
2443+
2444+
LogicalResult
2445+
matchAndRewrite(arith::DivFOp divOp, OpAdaptor adaptor,
2446+
ConversionPatternRewriter &rewriter) const override {
2447+
Type srcType = adaptor.getLhs().getType();
2448+
2449+
// Check if LHS is defined by an operation
2450+
auto *defOp = divOp.getLhs().getDefiningOp();
2451+
if (!defOp)
2452+
return failure();
2453+
2454+
auto constOp = dyn_cast<arith::ConstantOp>(defOp);
2455+
if (!constOp)
2456+
return failure();
2457+
2458+
// Handle scalar f32 case
2459+
if (auto fType = dyn_cast<FloatType>(srcType)) {
2460+
if (fType.getWidth() != 32)
2461+
return failure();
2462+
2463+
auto floatAttr = dyn_cast<FloatAttr>(constOp.getValue());
2464+
if (!floatAttr || !floatAttr.getValue().isExactlyValue(1.0))
2465+
return failure();
2466+
2467+
rewriter.replaceOpWithNewOp<aievec::InvOp>(divOp, srcType,
2468+
adaptor.getRhs());
2469+
return success();
2470+
}
2471+
2472+
// Handle vector f32 case
2473+
if (auto vecType = dyn_cast<VectorType>(srcType)) {
2474+
auto elemType = vecType.getElementType();
2475+
if (!elemType.isF32())
2476+
return failure();
2477+
2478+
// Check for supported vector sizes (16 or 32 lanes)
2479+
unsigned laneSize = getVectorLaneSize(vecType);
2480+
if (laneSize != 16 && laneSize != 32)
2481+
return failure();
2482+
2483+
// Check if it's a splat of 1.0
2484+
auto denseAttr = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
2485+
if (!denseAttr || !denseAttr.isSplat())
2486+
return failure();
2487+
2488+
if (!denseAttr.getSplatValue<APFloat>().isExactlyValue(1.0))
2489+
return failure();
2490+
2491+
rewriter.replaceOpWithNewOp<aievec::InvOp>(divOp, vecType,
2492+
adaptor.getRhs());
2493+
return success();
2494+
}
2495+
2496+
return failure();
2497+
}
2498+
};
2499+
24292500
// Convert math.tanh to a function call to compute tanh(x) by look up tables
24302501
struct ComputeTanhOpByLUTPattern : OpConversionPattern<math::TanhOp> {
24312502
using OpConversionPattern::OpConversionPattern;
@@ -3508,6 +3579,7 @@ populateAIEVecV2CommonConversionPatterns(RewritePatternSet &patterns,
35083579
>(patterns.getContext(), 128, 1024, 256, 1024);
35093580
patterns.add<
35103581
ComputeExpOpByLUTPattern,
3582+
ComputeInvOpByLUTPattern,
35113583
ComputeRsqrtOpPattern,
35123584
LowerVectorAddFOpToAIEVecAddElemOp,
35133585
LowerVectorSubFOpToAIEVecSubElemOp,
@@ -3521,7 +3593,6 @@ populateAIEVecV2CommonConversionPatterns(RewritePatternSet &patterns,
35213593
>(patterns.getContext());
35223594
}
35233595
patterns.add<
3524-
ComputeInvOpByLUTPattern,
35253596
ComputeTanhOpByLUTPattern,
35263597
ComputeSqrtOpAIE2Pattern,
35273598
ComputeErfOpPattern,
@@ -3629,10 +3700,11 @@ static void populateAIEVecV2PConversionPatterns(RewritePatternSet &patterns,
36293700
// AIE2p-specific broadcast pattern that handles 256-bit directly
36303701
patterns.add<ConvertSplatToAIEBroadcastAIE2p>(patterns.getContext());
36313702
patterns.add<LowerVectorReductionAddBfloat16OpAIE2P>(patterns.getContext());
3632-
// For AIE2P with LLVMIR backend, use aievec.exp
3703+
// For AIE2P with LLVMIR backend, use aievec.exp and aievec.inv
36333704
// math.rsqrt is kept legal and will be lowered in AIEVecToLLVM pass
36343705
if (backend == TargetBackend::LLVMIR) {
3635-
patterns.add<ConvertMathExpToAIEVecExpOpPattern>(patterns.getContext());
3706+
patterns.add<ConvertMathExpToAIEVecExpOpPattern,
3707+
ConvertDivFToAIEVecInvOpPattern>(patterns.getContext());
36363708
}
36373709
}
36383710

@@ -4066,6 +4138,41 @@ static void configureAIEVecV2PLegalizations(ConversionTarget &target,
40664138

40674139
return false;
40684140
});
4141+
4142+
// AIE2P-specific legalization for divf 1.0/x pattern with LLVMIR backend
4143+
// Scalar f32 or vector<Nxf32> divf with constant 1.0 LHS is illegal
4144+
target.addDynamicallyLegalOp<arith::DivFOp>([](arith::DivFOp divfOp) {
4145+
Type srcType = divfOp.getLhs().getType();
4146+
4147+
// Check if LHS is defined by a constant operation
4148+
auto constOp =
4149+
dyn_cast_or_null<arith::ConstantOp>(divfOp.getLhs().getDefiningOp());
4150+
if (!constOp)
4151+
return true;
4152+
4153+
// Scalar f32 case - check for exactly 1.0
4154+
if (srcType.isF32()) {
4155+
auto floatAttr = dyn_cast<FloatAttr>(constOp.getValue());
4156+
if (floatAttr && floatAttr.getValue().isExactlyValue(1.0))
4157+
return false; // illegal - will be converted to aievec.inv
4158+
return true;
4159+
}
4160+
4161+
// Vector f32 case - check for splat of exactly 1.0
4162+
if (auto vecType = dyn_cast<VectorType>(srcType)) {
4163+
if (vecType.getElementType().isF32()) {
4164+
unsigned laneSize = getVectorLaneSize(vecType);
4165+
if (laneSize == 16 || laneSize == 32) {
4166+
auto denseAttr = dyn_cast<DenseFPElementsAttr>(constOp.getValue());
4167+
if (denseAttr && denseAttr.isSplat() &&
4168+
denseAttr.getSplatValue<APFloat>().isExactlyValue(1.0))
4169+
return false; // illegal - will be converted to aievec.inv
4170+
}
4171+
}
4172+
}
4173+
4174+
return true;
4175+
});
40694176
}
40704177
// For CPP backend, exp remains legal (uses LUT pattern from common patterns)
40714178

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
//===- test-inv-aie2p.mlir -----------------------------------------*- MLIR -*-===//
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+
// Copyright (C) 2026, Advanced Micro Devices, Inc.
8+
//
9+
//===----------------------------------------------------------------------===//
10+
11+
// RUN: aie-opt %s -split-input-file --convert-aievec-to-llvm="aie-target=aie2p" | FileCheck %s
12+
13+
// Test: aievec.inv → xllvm.intr.aie2p.inv for AIE2P
14+
15+
// CHECK-LABEL: @test_inv_f32
16+
// CHECK-SAME: %[[ARG:.*]]: f32
17+
func.func @test_inv_f32(%arg0 : f32) -> f32 {
18+
// CHECK: %[[INV:.*]] = "xllvm.intr.aie2p.inv"(%[[ARG]]) : (f32) -> f32
19+
%0 = aievec.inv %arg0 : f32
20+
// CHECK: return %[[INV]] : f32
21+
return %0 : f32
22+
}
23+
24+
// -----
25+
26+
// CHECK-LABEL: @test_inv_v16f32
27+
// CHECK-SAME: %[[ARG:.*]]: vector<16xf32>
28+
func.func @test_inv_v16f32(%arg0 : vector<16xf32>) -> vector<16xf32> {
29+
// CHECK: %[[POISON:.*]] = llvm.mlir.poison : vector<16xf32>
30+
// CHECK: %[[IDX0:.*]] = llvm.mlir.constant(0 : i64) : i64
31+
// CHECK: %[[ELEM0:.*]] = llvm.extractelement %[[ARG]][%[[IDX0]] : i64] : vector<16xf32>
32+
// CHECK: %[[INV0:.*]] = "xllvm.intr.aie2p.inv"(%[[ELEM0]]) : (f32) -> f32
33+
// CHECK: %[[RES0:.*]] = llvm.insertelement %[[INV0]], {{.*}}[%[[IDX0]] : i64] : vector<16xf32>
34+
%0 = aievec.inv %arg0 : vector<16xf32>
35+
return %0 : vector<16xf32>
36+
}

0 commit comments

Comments
 (0)