Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/isa/micro-isa/09-conversion-ops.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ destination lane group.
- `%dst = pto.vcvt %src, %mask {rnd, sat, part} : !pto.vreg<64xf32>, !pto.mask<b32> -> !pto.vreg<128xf16>`
- `%dst = pto.vcvt %src, %mask {rnd, sat, part} : !pto.vreg<64xf32>, !pto.mask<b32> -> !pto.vreg<128xbf16>`
- `%dst = pto.vcvt %src, %mask {rnd, sat} : !pto.vreg<128xbf16>, !pto.mask<b16> -> !pto.vreg<128xf16>`
- `%dst = pto.vcvt %src, %mask {rnd} : !pto.vreg<128xf16>, !pto.mask<b16> -> !pto.vreg<128xbf16>`
- `%dst = pto.vcvt %src, %mask {part} : !pto.vreg<128xf16>, !pto.mask<b16> -> !pto.vreg<64xf32>`
- `%dst = pto.vcvt %src, %mask {part} : !pto.vreg<128xbf16>, !pto.mask<b16> -> !pto.vreg<64xf32>`

Expand Down Expand Up @@ -198,7 +199,7 @@ per-form entries above as the source of truth.
| `ui32` | Y | | Y | Y | | | | | | |
| `si32` | Y | | Y | Y | | | Y | | Y | |
| `si64` | | | | | | | | | | |
| `f16` | Y | Y | | Y | | Y | | | Y | |
| `f16` | Y | Y | | Y | | Y | | | Y | Y |
| `f32` | | | | Y | | Y | Y | Y | | Y |
| `bf16` | | | | | | Y | | Y | Y | |

Expand Down
6 changes: 3 additions & 3 deletions docs/isa/vmi-isa/06-convert.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

2. **FpNarrow** — `fp → fp`, `|dst| < |src|` (e.g. `f32 → f16`,
`f32 → bf16`, `f32 → fp8_e4m3`, `bf16x2 → f4x2`). Same-width `fp → fp`
(`|dst| == |src|`, e.g. `bf16 → f16`).
(`|dst| == |src|`, e.g. `bf16 → f16`, `f16 → bf16`).

3. **FpToSi** — `fp → signed int`. Supported pairs follow the contract
table `lookupVMIFpToSiContract`: `f32→s32`, `f16→s16`, `f32→s16`,
Expand Down Expand Up @@ -58,7 +58,7 @@
| Attribute | Values | Valid for | Description |
|---|---|---|---|
| `rounding` | `"R"` (nearest-even), `"A"` (away-from-zero), `"H"` (half-up), `"Z"` (toward-zero); for the `bf16x2→f4x2` contract pair the allowed set is `"R"`,`"A"`,`"F"` (floor), `"C"` (ceil), `"Z"` (toward-zero) — `"H"` is **rejected** | fp narrowing | Rounding mode |
| `saturate` | `"SAT"`, `"NOSAT"` | required for fp-narrow / int-narrow; for fp→si / fp→ui the requirement follows the vcvt contract's `requiresSat` (e.g. `f16→s8` required, `f16→s32` **forbidden** — no overflow possible; same-width `bf16→f16` required); the `bf16x2→f4x2` narrow has `requiresSat=false` — any `saturate` is **forbidden** | `SAT` clamps to ±max of the destination type; `NOSAT` performs a direct bit truncation of the result representation. |
| `saturate` | `"SAT"`, `"NOSAT"` | required for fp-narrow / int-narrow; for fp→si / fp→ui the requirement follows the vcvt contract's `requiresSat` (e.g. `f16→s8` required, `f16→s32` **forbidden** — no overflow possible; same-width `bf16→f16` required, same-width `f16→bf16` **forbidden**); the `bf16x2→f4x2` narrow has `requiresSat=false` — any `saturate` is **forbidden** | `SAT` clamps to ±max of the destination type; `NOSAT` performs a direct bit truncation of the result representation. |

- **datatypes:** Source and destination from `{f32, f16, bf16, fp8_e4m3, fp8_e5m2, i32, i16, i8, ui32, ui16, ui8}`; packed carrier types `{!pto.bf16x2, !pto.f4E1M2x2, !pto.f4E2M1x2}` for the bf16x2↔f4x2 fp-to-fp pair (see contract `lookupVMIFpToFpContract`). `bf16x2` is **conversion-only** — it may not appear as a compute element type (`vfadd`/`vfmul`/`vcmp`/...).
- **lowering to `pto.mi`:**
Expand All @@ -69,7 +69,7 @@
| 8↔32 (radix-4) | widen: `UNPK_B8` + `vintlv` + `vcvt P0` + `punpack`; narrow: `PK4_B32` store (or `vselr` gather) + `ppack` | `2–3` | `2–3` |
| f32→fp8 quant | `1 cast` + `PK4_B32` | `K` | `1` |
| f32→int8 quant | 3-stage cast + `PK4_B32` | `~3K` | `3` |
| fp↔fp same-width (`bf16→f16`) | `K × vcvt` (1:1, no part) | `K` | `1` |
| fp↔fp same-width (`bf16→f16`, `f16→bf16`) | `K × vcvt` (1:1, no part) | `K` | `1` |
| fp→si / fp→ui | per contract pair: same-width 1:1, widen EVEN/ODD, narrow EVEN/ODD+Vor | `K`–`~3K` | `2`–`3` |
| int↔int (same width) | `K × vtrc` or `K × vcvt` | `K` | `1` |
| `bf16x2→f4x2` narrow (32→8) | source viewed as raw `bf16` lanes (2 bf16/bf16x2); `vcvt{P0}` 1:1, `rnd` set, **no sat**; reuse prior pairing `vbitcast` when present | `K` | `1` |
Expand Down
30 changes: 23 additions & 7 deletions lib/PTO/IR/VMI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -813,7 +813,7 @@ lookupVMIFpToFpContract(Type srcElem, Type dstElem) {
return VMIFpToFpContract{/*requiresRnd=*/false, /*requiresSat=*/false,
/*requiresPart=*/true,
/*allowedRndModes=*/StringRef()};
}
}
if (srcBits != dstBits) {
return std::nullopt;
}
Expand All @@ -822,7 +822,15 @@ lookupVMIFpToFpContract(Type srcElem, Type dstElem) {
return VMIFpToFpContract{/*requiresRnd=*/true, /*requiresSat=*/true,
/*requiresPart=*/false,
/*allowedRndModes=*/StringRef()};
}
}
// f16 -> bf16: same-width, rnd, NO sat, no part.
bool srcIsF16 = srcElem.isF16();
bool dstIsBF16 = dstElem.isBF16();
if (srcIsF16 && dstIsBF16) {
return VMIFpToFpContract{/*requiresRnd=*/true, /*requiresSat=*/false,
/*requiresPart=*/false,
/*allowedRndModes=*/StringRef()};
}
return std::nullopt;
}

Expand Down Expand Up @@ -2304,11 +2312,19 @@ LogicalResult VMISIToFPOp::verify() {
if (!isVMIFloatLikeType(resultType.getElementType())) {
return emitOpError("requires floating-point-like result element type");
}
if (getVMIElementBitWidth(sourceType.getElementType()) != mlir::pto::kValue32) {
return emitOpError("requires 32-bit integer source element type");
}
if (!resultType.getElementType().isF32()) {
return emitOpError("requires f32 result element type");
unsigned srcBits = getVMIElementBitWidth(sourceType.getElementType());
if (srcBits == mlir::pto::kValue32) {
if (!resultType.getElementType().isF32()) {
return emitOpError("requires f32 result element type for 32-bit "
"integer source");
}
} else if (srcBits == mlir::pto::kValue8) {
if (!resultType.getElementType().isF16()) {
return emitOpError("requires f16 result element type for 8-bit "
"integer source");
}
} else {
return emitOpError("supports only si32 -> f32 or si8 -> f16");
}
return success();
}
Expand Down
3 changes: 3 additions & 0 deletions lib/PTO/IR/VPTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1771,6 +1771,9 @@ static std::optional<VcvtContract> lookupVcvtContract(VcvtElemKind src,
case VcvtElemKind::U8:
return VcvtContract{/*requiresRnd=*/true, /*requiresSat=*/true,
/*requiresPart=*/true};
case VcvtElemKind::BF16:
return VcvtContract{/*requiresRnd=*/true, /*requiresSat=*/false,
/*requiresPart=*/false};
default:
return std::nullopt;
}
Expand Down
167 changes: 123 additions & 44 deletions lib/PTO/Transforms/VMIToVPTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10869,7 +10869,8 @@ struct OneToNVMITruncFOpPattern : OneToNOpConversionPattern<VMITruncFOp> {

unsigned resultBits = pto::getPTOStorageElemBitWidth(
resultVRegTypes.front().getElementType());
// Same-width fp->fp (bf16 -> f16): dense contiguous 1:1, no part, rnd+sat.
// Same-width fp->fp (bf16 -> f16, f16 -> bf16): dense contiguous 1:1,
// no part; rnd always, sat follows the fp-to-fp contract.
if (sourceBits == resultBits && sourceLayout && resultLayout &&
sourceLayout.isContiguous() && sourceLayout.getLaneStride() == 1 &&
resultLayout.isContiguous() && resultLayout.getLaneStride() == 1 &&
Expand Down Expand Up @@ -12053,40 +12054,96 @@ struct OneToNVMISIToFPOpPattern : OneToNOpConversionPattern<VMISIToFPOp> {
ValueRange sourceParts = adaptor.getSource();
FailureOr<SmallVector<Type>> maybe_resultTypes =
getConvertedResultTypes(op, 0, *this->getTypeConverter());
if (failed(maybe_resultTypes))
if (failed(maybe_resultTypes)) {
return failure();
}
SmallVector<Type> resultTypes = std::move(*maybe_resultTypes);
if (sourceParts.size() != resultTypes.size())

auto sourceType = dyn_cast<VRegType>(sourceParts.front().getType());
if (!sourceType || !isa<IntegerType>(sourceType.getElementType())) {
return rewriter.notifyMatchFailure(
op, "sitofp physical source/result arity mismatch");
op, "sitofp requires integer source chunks");
}
unsigned sourceBits =
pto::getPTOStorageElemBitWidth(sourceType.getElementType());

SmallVector<VRegType> resultVRegTypes;
resultVRegTypes.reserve(resultTypes.size());
for (Type resultType : resultTypes) {
auto resultVRegType = dyn_cast<VRegType>(resultType);
if (!resultVRegType ||
(!resultVRegTypes.empty() &&
resultVRegType != resultVRegTypes.front())) {
return rewriter.notifyMatchFailure(
op, "unsupported physical sitofp result type");
}
resultVRegTypes.push_back(resultVRegType);
}
unsigned resultBits = pto::getPTOStorageElemBitWidth(
resultVRegTypes.front().getElementType());

FailureOr<Value> mask =
createAllTrueMaskForVReg(op.getLoc(), sourceType, rewriter);
if (failed(mask)) {
return rewriter.notifyMatchFailure(op, "failed to build sitofp mask");
}

SmallVector<Value> results;
results.reserve(resultTypes.size());
StringAttr rnd = rewriter.getStringAttr("R");
for (auto [sourcePart, resultType] :
llvm::zip_equal(sourceParts, resultTypes)) {
auto sourceType = dyn_cast<VRegType>(sourcePart.getType());
auto resultVRegType = dyn_cast<VRegType>(resultType);
if (!sourceType || !isa<IntegerType>(sourceType.getElementType()) ||
pto::getPTOStorageElemBitWidth(sourceType.getElementType()) != 32 ||
!resultVRegType || !resultVRegType.getElementType().isF32())

// si32 -> f32: same-width 1:1, no part, rnd=R.
if (sourceBits == 32 && resultBits == 32) {
size_t srcArity = sourceParts.size();
size_t dstArity = resultTypes.size();
if (srcArity != dstArity) {
return rewriter.notifyMatchFailure(
op, "sitofp requires physical 32-bit integer source and f32 "
"result chunks");
op, "si32->f32 requires matching physical arity");
}
StringAttr rnd = rewriter.getStringAttr("R");
for (auto [sourcePart, resultVRegType] :
llvm::zip_equal(sourceParts, resultVRegTypes)) {
results.push_back(rewriter
.create<VcvtOp>(op.getLoc(), resultVRegType,
sourcePart, *mask, rnd,
/*sat=*/nullptr,
/*part=*/nullptr)
.getResult());
}
replaceOpWithFlatConvertedValues(rewriter, op, results,
*this->getTypeConverter());
return success();
}

FailureOr<Value> mask =
createAllTrueMaskForVReg(op.getLoc(), sourceType, rewriter);
if (failed(mask))
return rewriter.notifyMatchFailure(op, "failed to build sitofp mask");
results.push_back(rewriter
.create<VcvtOp>(op.getLoc(), resultVRegType,
sourcePart, *mask, rnd,
/*sat=*/nullptr, /*part=*/nullptr)
.getResult());
// si8 -> f16: 8->16 widening, EvenOdd parts, no rnd/sat.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR 描述里说改了 lib/PTO/Transforms/VMILowerUnifiedToLegacy.cpplowerVCvt(不再把 s8→f16 分解为 extsi→sitofp→truncf,而是直接创建 VMISIToFPOp),但这次 diff 里并没有包含这个文件。

需要确认一下:

  • 如果这条改动在 rebase 中丢了,那么"不再走三步分解"的前提是否还在 main 上成立?当前的 lowerVCvt 对 s8→f16 实际走的是什么路径?
  • 如果 main 上已经是直接创建 VMISIToFPOp(无需再改),请更新 PR 描述,避免误导 reviewer。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

该描述已过时,lowerVCvt 对 s8→f16 实际走的已经是直接lowering成对应vpto的形式。已更新pr描述。

if (sourceBits == 8 && resultBits == 16) {
size_t expectedResults = 2 * sourceParts.size();
size_t actualResults = resultTypes.size();
if (actualResults != expectedResults) {
return rewriter.notifyMatchFailure(
op, "si8->f16 requires result arity = 2 x source arity");
}
static constexpr StringRef kEvenOddParts[] = {"EVEN", "ODD"};
for (int64_t partIndex = 0; partIndex < 2; ++partIndex) {
for (auto [chunkIndex, sourcePart] :
llvm::enumerate(sourceParts)) {
VRegType resultType =
resultVRegTypes[partIndex * sourceParts.size() + chunkIndex];
results.push_back(
rewriter
.create<VcvtOp>(op.getLoc(), resultType, sourcePart, *mask,
/*rnd=*/nullptr, /*sat=*/nullptr,
rewriter.getStringAttr(
kEvenOddParts[partIndex]))
.getResult());
}
}
replaceOpWithFlatConvertedValues(rewriter, op, results,
*this->getTypeConverter());
return success();
}

replaceOpWithFlatConvertedValues(rewriter, op, results, *this->getTypeConverter());
return success();
return rewriter.notifyMatchFailure(
op, "unsupported sitofp source/result width relation");
}
};

Expand Down Expand Up @@ -12808,39 +12865,60 @@ LogicalResult checkSupportedFPToUIShape(VMIFPToUIOp op,
LogicalResult checkSupportedSIToFPShape(VMISIToFPOp op,
std::string *reason = nullptr) {
auto fail = [&](const Twine &message) {
if (reason)
if (reason) {
*reason = message.str();
}
return failure();
};

auto sourceType = cast<VMIVRegType>(op.getSource().getType());
auto resultType = cast<VMIVRegType>(op.getResult().getType());
VMILayoutAttr sourceLayout = sourceType.getLayoutAttr();
VMILayoutAttr resultLayout = resultType.getLayoutAttr();
if (!sourceLayout || !resultLayout)
if (!sourceLayout || !resultLayout) {
return fail("requires assigned source/result layouts");
if (sourceLayout != resultLayout)
return fail("requires source/result layouts to match");
if (!isa<IntegerType>(sourceType.getElementType()) ||
pto::getPTOStorageElemBitWidth(sourceType.getElementType()) != 32)
return fail("requires 32-bit integer source element type");
if (!resultType.getElementType().isF32())
return fail("requires f32 result element type");
FailureOr<int64_t> sourceArity = getVMIPhysicalArity(sourceType);
FailureOr<int64_t> resultArity = getVMIPhysicalArity(resultType);
if (failed(sourceArity) || failed(resultArity) ||
*sourceArity != *resultArity)
return fail("requires matching computable physical arity");
}
unsigned srcBits = pto::getPTOStorageElemBitWidth(sourceType.getElementType());
unsigned dstBits = pto::getPTOStorageElemBitWidth(resultType.getElementType());
if (srcBits == 32 && dstBits == 32) {
if (sourceLayout != resultLayout) {
return fail("si32->f32 requires matching layouts");
}
if (!resultType.getElementType().isF32()) {
return fail("requires f32 result element type");
}
FailureOr<int64_t> sourceArity = getVMIPhysicalArity(sourceType);
FailureOr<int64_t> resultArity = getVMIPhysicalArity(resultType);
bool aritiesOk = succeeded(sourceArity) && succeeded(resultArity) &&
*sourceArity == *resultArity;
if (!aritiesOk) {
return fail("requires matching computable physical arity");
}
} else if (srcBits == 8 && dstBits == 16) {
if (!resultType.getElementType().isF16()) {
return fail("requires f16 result element type");
}
VMILayoutSupport layoutSupport;
if (failed(layoutSupport.getCastLayoutFactForLayouts(
sourceType, resultType, sourceLayout, resultLayout, reason))) {
return failure();
}
} else {
return fail("supports only si32 -> f32 or si8 -> f16");
}
return success();
}

LogicalResult checkSupportedBitcastShape(VMIBitcastOp op, std::string *reason) {
VMILayoutSupport supports;
if (failed(supports.getBitcastSupport(op, reason)))
if (failed(supports.getBitcastSupport(op, reason))) {
return failure();
}
return success();
}



LogicalResult
checkSupportedChannelSplitShape(VMIChannelSplitOp op,
std::string *reason = nullptr) {
Expand Down Expand Up @@ -14221,13 +14299,13 @@ verifySupportedVMIToVPTOOps(ModuleOp module,

if (auto sitofp = dyn_cast<VMISIToFPOp>(op)) {
std::string reason;
if (succeeded(checkSupportedSIToFPShape(sitofp, &reason)))
if (succeeded(checkSupportedSIToFPShape(sitofp, &reason))) {
return WalkResult::advance();
}

sitofp.emitError()
<< kVMIDiagUnsupportedPrefix
<< "pto.vmi.sitofp supports 32-bit integer source chunks to "
"matching f32 result chunks with identical assigned layouts ("
<< "pto.vmi.sitofp supports si32->f32 or si8->f16 conversion shapes ("
<< reason << ")";
return WalkResult::interrupt();
}
Expand Down Expand Up @@ -14286,8 +14364,9 @@ verifySupportedVMIToVPTOOps(ModuleOp module,

if (auto bitcast = dyn_cast<VMIBitcastOp>(op)) {
std::string reason;
if (succeeded(checkSupportedBitcastShape(bitcast, &reason)))
if (succeeded(checkSupportedBitcastShape(bitcast, &reason))) {
return WalkResult::advance();
}

bitcast.emitError()
<< kVMIDiagUnsupportedPrefix
Expand Down
1 change: 1 addition & 0 deletions lib/PTO/Transforms/VPTOCANN900LLVMEmitterTypeHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1280,6 +1280,7 @@ constexpr VcvtContractEntry kVcvtContractEntries[] = {
{VcvtElemKind::F16, VcvtElemKind::F8E5M2, {"llvm.hivm.vcvtff.f162f8e5m2.x", true, true, true, 16, false}},
{VcvtElemKind::F16, VcvtElemKind::HiF8, {"llvm.hivm.vcvtff.f162hif8.x", true, true, true, 16, false}},
{VcvtElemKind::F16, VcvtElemKind::F32, {"llvm.hivm.vcvtff.f162f32.x", false, false, true, 16, false}},
{VcvtElemKind::F16, VcvtElemKind::BF16, {"llvm.hivm.vcvtff.f162bf16.x", true, false, false, 16, false}},
{VcvtElemKind::F16, VcvtElemKind::S32, {"llvm.hivm.vcvtfi.f162s32.x", true, false, true, 16, false}},
{VcvtElemKind::F16, VcvtElemKind::S16, {"llvm.hivm.vcvtfi.f162s16.x", true, true, false, 16, false}},
{VcvtElemKind::F16, VcvtElemKind::S8, {"llvm.hivm.vcvtfi.f162s8.x", true, true, true, 16, false}},
Expand Down
2 changes: 2 additions & 0 deletions lib/PTO/Transforms/VPTOLLVMEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1662,6 +1662,8 @@ static std::optional<VcvtContract> lookupVcvtContract(VcvtElemKind src,
return VcvtContract{"llvm.hivm.vcvtfi.f162s8.x", true, true, true, 16};
case VcvtElemKind::U8:
return VcvtContract{"llvm.hivm.vcvtfi.f162u8.x", true, true, true, 16};
case VcvtElemKind::BF16:
return VcvtContract{"llvm.hivm.vcvtff.f162bf16.x", true, false, false, 16};
default:
return std::nullopt;
}
Expand Down
3 changes: 3 additions & 0 deletions test/lit/vmi_new/vmi_conversion_contract_matrix.pto
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
module {
func.func @positive(%f16: !pto.vmi.vreg<64xf16>, %f32: !pto.vmi.vreg<64xf32>, %si8: !pto.vmi.vreg<64xsi8>, %ui8: !pto.vmi.vreg<64xui8>, %si16: !pto.vmi.vreg<64xsi16>, %si32: !pto.vmi.vreg<64xsi32>) {
%fw = pto.vmi.vcvt %f16 : !pto.vmi.vreg<64xf16> -> !pto.vmi.vreg<64xf32>
%fb = pto.vmi.vcvt %f16 {rounding = "R"} : !pto.vmi.vreg<64xf16> -> !pto.vmi.vreg<64xbf16>
%fn_r = pto.vmi.vcvt %f32 {rounding = "R", saturate = "SAT"} : !pto.vmi.vreg<64xf32> -> !pto.vmi.vreg<64xf16>
%fn_a = pto.vmi.vcvt %f32 {rounding = "A", saturate = "NOSAT"} : !pto.vmi.vreg<64xf32> -> !pto.vmi.vreg<64xf16>
%fn_h = pto.vmi.vcvt %f32 {rounding = "H", saturate = "SAT"} : !pto.vmi.vreg<64xf32> -> !pto.vmi.vreg<64xbf16>
Expand Down Expand Up @@ -66,3 +67,5 @@ module {
return
}
}

// CHECK: pto.vmi.vcvt {{.*}} {rounding = "R"} : !pto.vmi.vreg<64xf16> -> !pto.vmi.vreg<64xbf16>
Loading
Loading