From 93a6b87b0414ccd46eb71e0de017c4d429530f3b Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Tue, 11 Aug 2026 15:23:32 +0800 Subject: [PATCH 01/32] feat(linx): adopt PTO ISA v0.58 --- .github/workflows/ci.yml | 10 + CMakeLists.txt | 4 +- README.md | 7 + include/PTO/IR/PTO.h | 4 + include/PTO/IR/PTOOps.td | 58 +++ lib/PTO/IR/PTO.cpp | 70 ++- lib/PTO/IR/PTOTypeDefs.cpp | 2 + lib/PTO/Transforms/PTOToEmitC.cpp | 70 ++- test/lit/pto/v058_deleted_ops_rejected.pto | 11 + test/lit/pto/v058_linx_target.pto | 12 + test/lit/pto/v058_tfma_gmov_emitc.pto | 21 + tools/check_v058_pto_manifest.py | 453 ++++++++++++++++++ tools/pto_isa_v0_58_0_lock.json | 47 ++ .../pto_isa_v0_58_0_operation_contracts.json | 76 +++ tools/ptoas/ptoas.cpp | 53 +- tools/ptobc/generated/ptobc_opcodes_v0.h | 6 + tools/ptobc/tests/CMakeLists.txt | 6 +- tools/ptobc/tests/opcode_coverage_check.py | 16 + tools/ptobc/tests/v0580_ops_v0_encode.sh | 25 + 19 files changed, 928 insertions(+), 23 deletions(-) create mode 100644 test/lit/pto/v058_deleted_ops_rejected.pto create mode 100644 test/lit/pto/v058_linx_target.pto create mode 100644 test/lit/pto/v058_tfma_gmov_emitc.pto create mode 100644 tools/check_v058_pto_manifest.py create mode 100644 tools/pto_isa_v0_58_0_lock.json create mode 100644 tools/pto_isa_v0_58_0_operation_contracts.json create mode 100644 tools/ptobc/tests/v0580_ops_v0_encode.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4064a2c92e..952aa0acee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -213,6 +213,16 @@ jobs: export PYTHONPATH="${MLIR_PYTHONPATH}:${PTO_INSTALL_DIR}:${PYTHONPATH:-}" ninja -C build check-pto + - name: Run PTO ISA v0.58 contract gates + shell: bash + run: | + python3 tools/check_v058_pto_manifest.py --ptoas-root . + python3 tools/ptobc/tests/opcode_coverage_check.py \ + include/PTO/IR/PTOOps.td \ + tools/ptobc/generated/ptobc_opcodes_v0.h + ctest --test-dir build --output-on-failure \ + -R 'pto_isa_v0580_contract_check|ptobc_v0580_ops_v0_encode|ptobc_tpartarg_v0_encode' + - name: Run sample tests (py -> pto -> cpp) shell: bash env: diff --git a/CMakeLists.txt b/CMakeLists.txt index e5608ee153..14b7fea43e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,9 +129,9 @@ if(BUILD_TESTING) enable_testing() find_package(Python3 COMPONENTS Interpreter REQUIRED) add_test( - NAME pto_isa_v0571_contract_check + NAME pto_isa_v0580_contract_check COMMAND ${Python3_EXECUTABLE} - ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_v057_pto_manifest.py + ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_v058_pto_manifest.py --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(tools/ptobc/tests) endif() diff --git a/README.md b/README.md index 6e7bc1f5bb..3738ec35d8 100644 --- a/README.md +++ b/README.md @@ -207,6 +207,9 @@ ptoas test/lit/pto/empty_func.pto --enable-insert-sync -o outputfile.cpp # 指定目标硬件架构(A3 / A5) ptoas test/lit/pto/empty_func.pto --pto-arch=a5 -o outputfile.cpp +# LinxISA v0.58 必须使用独立 Linx target,不得借用 A3/A5 target +ptoas test/lit/pto/v058_linx_target.pto --pto-arch=linx -o outputfile.cpp + # 指定构建 Level(level3 会禁用 PlanMemory/InsertSync) ptoas test/lit/pto/empty_func.pto --pto-level=level3 -o outputfile.cpp @@ -215,6 +218,10 @@ ptoas --version ``` +`linx` target 对齐受管的 `Linx-TileOP-API`:生成代码包含 +`jcore/template_asm.hpp`,只接受 v0.58 公共 PTO 操作目录,并在 lowering +前拒绝仅属于 A3/A5 方言面的操作。 + ### 5.2 Python 接口 (Python API) 配置好环境变量后,PTO Dialect 将作为 `mlir.dialects` 的一部分被加载。 diff --git a/include/PTO/IR/PTO.h b/include/PTO/IR/PTO.h index b8c6f90b23..d31ab2a82a 100644 --- a/include/PTO/IR/PTO.h +++ b/include/PTO/IR/PTO.h @@ -86,6 +86,7 @@ bool isScalarPtrOrMemRef(Type type); enum class PTOArch { A3, A5, + Linx, }; /// Resolve the effective PTO target architecture from module-level IR state. @@ -93,13 +94,16 @@ PTOArch getTargetArch(ModuleOp module); PTOArch getTargetArch(Operation *op); bool isTargetArchA3(ModuleOp module); bool isTargetArchA5(ModuleOp module); +bool isTargetArchLinx(ModuleOp module); bool isTargetArchA3(Operation *op); bool isTargetArchA5(Operation *op); +bool isTargetArchLinx(Operation *op); enum class PTOParserTargetArch { Unspecified, A3, A5, + Linx, }; void setPTOParserTargetArch(MLIRContext *context, PTOParserTargetArch arch); diff --git a/include/PTO/IR/PTOOps.td b/include/PTO/IR/PTOOps.td index 22e5ad3f34..c3aa727b0b 100644 --- a/include/PTO/IR/PTOOps.td +++ b/include/PTO/IR/PTOOps.td @@ -1179,6 +1179,34 @@ def TMovOp : PTO_TOp<"tmov", [ }]; } +def GMovOp : PTO_TOp<"gmov", [ + PTO_DpsInitOpInterface, + OpPipeInterface, + DeclareOpInterfaceMethods +]> { + let summary = "Move data from a peer thread into a destination tile."; + + let arguments = (ins + PTODpsType:$src, + I64:$peer_tid, + PTODpsType:$dst + ); + + let results = (outs); + + let assemblyFormat = [{ + `ins` `(` $src `,` $peer_tid `:` qualified(type($src)) `,` type($peer_tid) `)` + `outs` `(` $dst `:` qualified(type($dst)) `)` attr-dict + }]; + + let hasVerifier = 1; + + let extraClassDeclaration = [{ + ::mlir::pto::PIPE getPipe() { return ::mlir::pto::PIPE::PIPE_MTE2; } + ::mlir::MutableOperandRange getDpsInitsMutable() { return getDstMutable(); } + }]; +} + //===----------------------------------------------------------------------===// // Pointer Cast Op (existing) @@ -2877,6 +2905,36 @@ def TAddOp : PTO_TOp<"tadd", [ }]; } +def TFmaOp : PTO_TOp<"tfma", [ + PTO_DpsInitOpInterface, + OpPipeInterface, + DeclareOpInterfaceMethods +]> { + let summary = "Elementwise fused multiply-add of three tiles."; + + let arguments = (ins + PTODpsType:$src0, + PTODpsType:$src1, + PTODpsType:$src2, + PTODpsType:$dst + ); + + let results = (outs); + + let assemblyFormat = [{ + `ins` `(` $src0 `,` $src1 `,` $src2 `:` qualified(type($src0)) `,` + qualified(type($src1)) `,` qualified(type($src2)) `)` + `outs` `(` $dst `:` qualified(type($dst)) `)` attr-dict + }]; + + let hasVerifier = 1; + + let extraClassDeclaration = [{ + ::mlir::pto::PIPE getPipe() { return ::mlir::pto::PIPE::PIPE_V; } + ::mlir::MutableOperandRange getDpsInitsMutable() { return getDstMutable(); } + }]; +} + def TAddSOp : PTO_TOp<"tadds", [ PTO_DpsInitOpInterface, OpPipeInterface, diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index 7715b9d26b..6537221f99 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -93,6 +93,7 @@ enum class VerifierTargetArch { }; static VerifierTargetArch getVerifierTargetArch(Operation *op); static std::optional getVerifierArchName(Operation *op); +static bool isVerifierTargetLinx(Operation *op); static bool isSupportedVecElemType(Type ty, bool allowBf16 = true, bool allowInt8 = true); static bool isSupportedLoadStoreElemTypeA2A3(Type ty); @@ -247,6 +248,8 @@ PTOArch mlir::pto::getTargetArch(ModuleOp module) { return PTOArch::A3; auto arch = module->getAttrOfType(kPTOTargetArchAttrName); + if (arch && arch.getValue().equals_insensitive("linx")) + return PTOArch::Linx; if (arch && arch.getValue().equals_insensitive("a5")) return PTOArch::A5; return PTOArch::A3; @@ -266,6 +269,10 @@ bool mlir::pto::isTargetArchA5(ModuleOp module) { return getTargetArch(module) == PTOArch::A5; } +bool mlir::pto::isTargetArchLinx(ModuleOp module) { + return getTargetArch(module) == PTOArch::Linx; +} + bool mlir::pto::isTargetArchA3(Operation *op) { return getTargetArch(op) == PTOArch::A3; } @@ -274,6 +281,10 @@ bool mlir::pto::isTargetArchA5(Operation *op) { return getTargetArch(op) == PTOArch::A5; } +bool mlir::pto::isTargetArchLinx(Operation *op) { + return getTargetArch(op) == PTOArch::Linx; +} + static llvm::TypeSize getOneByteTypeSize() { return llvm::TypeSize::getFixed(8); } @@ -325,12 +336,15 @@ uint64_t mlir::pto::F4E2M1x2Type::getPreferredAlignment( static VerifierTargetArch getVerifierTargetArch(Operation *op) { if (auto archName = getVerifierArchName(op)) { - return archName->equals_insensitive("a5") ? VerifierTargetArch::A5 - : VerifierTargetArch::A2A3; + return (archName->equals_insensitive("a5") || + archName->equals_insensitive("linx")) + ? VerifierTargetArch::A5 + : VerifierTargetArch::A2A3; } switch (getPTOParserTargetArch(op ? op->getContext() : nullptr)) { case PTOParserTargetArch::A5: + case PTOParserTargetArch::Linx: return VerifierTargetArch::A5; case PTOParserTargetArch::A3: case PTOParserTargetArch::Unspecified: @@ -349,6 +363,13 @@ static std::optional getVerifierArchName(Operation *op) { return std::nullopt; } +static bool isVerifierTargetLinx(Operation *op) { + if (auto archName = getVerifierArchName(op)) + return archName->equals_insensitive("linx"); + return getPTOParserTargetArch(op ? op->getContext() : nullptr) == + PTOParserTargetArch::Linx; +} + static bool shouldBypassDecodedMemrefVerifier(Operation *op) { if (!op) return false; @@ -3735,6 +3756,44 @@ LogicalResult pto::TAddOp::verify() { "expects A5 tadd element type to be i32/i16/i8/f16/bf16/f32"); } +LogicalResult pto::TFmaOp::verify() { + if (!isVerifierTargetLinx(getOperation())) + return emitOpError("is only available for --pto-arch=linx"); + Type src0Ty = getSrc0().getType(); + Type src1Ty = getSrc1().getType(); + Type src2Ty = getSrc2().getType(); + Type dstTy = getDst().getType(); + if (failed(verifyVecTileCommon(*this, src0Ty, "src0")) || + failed(verifyVecTileCommon(*this, src1Ty, "src1")) || + failed(verifyVecTileCommon(*this, src2Ty, "src2")) || + failed(verifyVecTileCommon(*this, dstTy, "dst"))) + return failure(); + if (getShapeVec(src0Ty) != getShapeVec(src1Ty) || + getShapeVec(src0Ty) != getShapeVec(src2Ty) || + getShapeVec(src0Ty) != getShapeVec(dstTy)) + return emitOpError("expects src0, src1, src2, and dst to have the same shape"); + if (getElemTy(src0Ty) != getElemTy(src1Ty) || + getElemTy(src0Ty) != getElemTy(src2Ty) || + getElemTy(src0Ty) != getElemTy(dstTy)) + return emitOpError("expects src0, src1, src2, and dst element types to match"); + return success(); +} + +LogicalResult pto::GMovOp::verify() { + if (!isVerifierTargetLinx(getOperation())) + return emitOpError("is only available for --pto-arch=linx"); + Type srcTy = getSrc().getType(); + Type dstTy = getDst().getType(); + if (failed(verifyTileBufCommon(*this, srcTy, "src")) || + failed(verifyTileBufCommon(*this, dstTy, "dst"))) + return failure(); + if (getShapeVec(srcTy) != getShapeVec(dstTy)) + return emitOpError("expects src and dst to have the same shape"); + if (getElemTy(srcTy) != getElemTy(dstTy)) + return emitOpError("expects src and dst element types to match"); + return success(); +} + LogicalResult pto::TAddSOp::verify() { return verifyArithmeticScalarTileOpWithArchDispatch( getOperation(), getSrc().getType(), getDst().getType(), getScalar().getType(), @@ -10164,6 +10223,13 @@ void SetValidShapeOp::getEffects( // Elementwise + reductions: mostly PIPE_V tilebuf ops PTO_DEFINE_BINARY_EFFECTS(TAddOp, getSrc0Mutable(), getSrc1Mutable(), getDstMutable()) +PTO_DEFINE_TERNARY_EFFECTS(TFmaOp, getSrc0Mutable(), getSrc1Mutable(), getSrc2Mutable(), getDstMutable()) +void GMovOp::getEffects( + SmallVectorImpl> &effects) { + PTO_ADD_READ(getSrcMutable()); + PTO_ADD_READ(getPeerTidMutable()); + PTO_ADD_WRITE(getDstMutable()); +} PTO_DEFINE_UNARY_EFFECTS(TAddSOp, getSrcMutable(), getDstMutable()) void TAxpyOp::getEffects( SmallVectorImpl> &effects) { diff --git a/lib/PTO/IR/PTOTypeDefs.cpp b/lib/PTO/IR/PTOTypeDefs.cpp index c929bb4eab..b1fab55048 100644 --- a/lib/PTO/IR/PTOTypeDefs.cpp +++ b/lib/PTO/IR/PTOTypeDefs.cpp @@ -172,6 +172,8 @@ static BLayout resolveTileBufBLayout(MLIRContext *context, return BLayout::RowMajor; case PTOParserTargetArch::A5: return BLayout::ColMajor; + case PTOParserTargetArch::Linx: + return parsedLayout; case PTOParserTargetArch::Unspecified: return parsedLayout; } diff --git a/lib/PTO/Transforms/PTOToEmitC.cpp b/lib/PTO/Transforms/PTOToEmitC.cpp index 1fe64e7373..812662e55b 100644 --- a/lib/PTO/Transforms/PTOToEmitC.cpp +++ b/lib/PTO/Transforms/PTOToEmitC.cpp @@ -2900,7 +2900,14 @@ struct FuncToEmitC : public OpConversionPattern { return success(); } - if (pto::isPTOEntryFunction(op)) { + const bool isLinx = pto::isTargetArchLinx(op.getOperation()); + if (isLinx && pto::isPTOEntryFunction(op)) { + emitcFunc.setSpecifiersAttr(rewriter.getStrArrayAttr({"extern \"C\""})); + } else if (isLinx && op.isPrivate()) { + emitcFunc.setSpecifiersAttr(rewriter.getStrArrayAttr({"static", "inline"})); + } else if (isLinx) { + emitcFunc.setSpecifiersAttr(rewriter.getStrArrayAttr({"inline"})); + } else if (pto::isPTOEntryFunction(op)) { emitcFunc.setSpecifiersAttr( rewriter.getStrArrayAttr({"__global__ AICORE"})); } else if (op.isPrivate()) { @@ -2910,8 +2917,10 @@ struct FuncToEmitC : public OpConversionPattern { emitcFunc.setSpecifiersAttr(rewriter.getStrArrayAttr({"AICORE"})); } - std::optional kernelKindMacro = getKernelKindMacro(op); - bool needsNoSplitGuard = needsA5NoSplitVectorGuard(op.getOperation()); + std::optional kernelKindMacro = + isLinx ? std::nullopt : getKernelKindMacro(op); + bool needsNoSplitGuard = + !isLinx && needsA5NoSplitVectorGuard(op.getOperation()); // Inline the original body, then convert region/block argument types to // match the converted signature (also covers CFG blocks introduced by @@ -5756,6 +5765,39 @@ struct PTOTAddToTADD : public OpConversionPattern { } }; +struct PTOTFmaToTFMA : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult matchAndRewrite(pto::TFmaOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Value src0 = peelUnrealized(adaptor.getSrc0()); + Value src1 = peelUnrealized(adaptor.getSrc1()); + Value src2 = peelUnrealized(adaptor.getSrc2()); + Value dst = peelUnrealized(adaptor.getDst()); + rewriter.create( + op.getLoc(), TypeRange{}, "TFMA", ArrayAttr{}, ArrayAttr{}, + ValueRange{dst, src0, src1, src2}); + rewriter.eraseOp(op); + return success(); + } +}; + +struct PTOGMovToGMOV : public OpConversionPattern { + using OpConversionPattern::OpConversionPattern; + + LogicalResult matchAndRewrite(pto::GMovOp op, OpAdaptor adaptor, + ConversionPatternRewriter &rewriter) const override { + Value src = peelUnrealized(adaptor.getSrc()); + Value peerTid = peelUnrealized(adaptor.getPeerTid()); + Value dst = peelUnrealized(adaptor.getDst()); + rewriter.create( + op.getLoc(), TypeRange{}, "GMOV", ArrayAttr{}, ArrayAttr{}, + ValueRange{dst, peerTid, src}); + rewriter.eraseOp(op); + return success(); + } +}; + struct PTOInitializeL2G2LPipeToEmitC : public OpConversionPattern { PTOInitializeL2G2LPipeToEmitC(TypeConverter &typeConverter, MLIRContext *ctx, @@ -11802,6 +11844,7 @@ static void populatePTOToEmitCPatterns(RewritePatternSet &patterns, patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); + patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); @@ -11896,6 +11939,7 @@ static void populatePTOToEmitCPatterns(RewritePatternSet &patterns, patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); + patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); @@ -12050,8 +12094,18 @@ struct EmitPTOManualPass auto loc = mop->getLoc(); OpBuilder builder(ctx); builder.setInsertionPointToStart(mop.getBody()); - builder.create( - loc, "pto/pto-inst.hpp", /*is_standard_include=*/false); + if (targetArch == PTOArch::Linx) { + builder.create( + loc, "jcore/template_asm.hpp", /*is_standard_include=*/false); + } else { + builder.create( + loc, "pto/pto-inst.hpp", /*is_standard_include=*/false); + } + if (targetArch == PTOArch::Linx && + (needsCommInclude || needsEventIdArrayHelper)) { + mop.emitError("Linx target does not yet support PTO communication/session helpers"); + return signalPassFailure(); + } if (needsCommInclude) { builder.create( loc, builder.getStringAttr(R"cpp( @@ -12087,8 +12141,9 @@ struct PTOAS_EventIdArray { }; )cpp")); } - builder.create( - loc, builder.getStringAttr(R"cpp( + if (targetArch != PTOArch::Linx) { + builder.create( + loc, builder.getStringAttr(R"cpp( enum class PTOAutoSyncTailMode : int { kBarrierAll = 0, kSetWaitMte3ToSEvent0 = 1, @@ -12108,6 +12163,7 @@ static AICORE inline void ptoas_auto_sync_tail( } } )cpp")); + } // Only inject the bitcast helper when we actually lower ops that need it // (e.g. arith.bitcast or arith.maximumf/minimumf tie-breaking on zeros). bool needsBitcastHelper = false; diff --git a/test/lit/pto/v058_deleted_ops_rejected.pto b/test/lit/pto/v058_deleted_ops_rejected.pto new file mode 100644 index 0000000000..47ef24a22d --- /dev/null +++ b/test/lit/pto/v058_deleted_ops_rejected.pto @@ -0,0 +1,11 @@ +// RUN: not ptoas --pto-arch=linx %s 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @v058_reject_acccvt() attributes {pto.kernel_kind = #pto.kernel_kind} { + %dst = pto.alloc_tile : !pto.tile_buf + pto.acccvt outs(%dst : !pto.tile_buf) + return + } +} + +// CHECK: error: 'pto.acccvt' op is dialect-only and not part of the active Linx PTO ISA v0.58.0 target diff --git a/test/lit/pto/v058_linx_target.pto b/test/lit/pto/v058_linx_target.pto new file mode 100644 index 0000000000..04097e50f3 --- /dev/null +++ b/test/lit/pto/v058_linx_target.pto @@ -0,0 +1,12 @@ +// RUN: ptoas --pto-arch=linx %s | FileCheck %s + +module { + func.func @linx_target_smoke() attributes {pto.kernel_kind = #pto.kernel_kind} { + return + } +} + +// CHECK: #include "jcore/template_asm.hpp" +// CHECK-NOT: pto/pto-inst.hpp +// CHECK-NOT: AICORE +// CHECK-LABEL: extern "C" void linx_target_smoke() diff --git a/test/lit/pto/v058_tfma_gmov_emitc.pto b/test/lit/pto/v058_tfma_gmov_emitc.pto new file mode 100644 index 0000000000..2c939c51f4 --- /dev/null +++ b/test/lit/pto/v058_tfma_gmov_emitc.pto @@ -0,0 +1,21 @@ +// RUN: ptoas --pto-arch=linx %s | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @v058_tfma_gmov() attributes {pto.kernel_kind = #pto.kernel_kind} { + %peer = arith.constant 1 : i64 + %a = pto.alloc_tile : !pto.tile_buf + %b = pto.alloc_tile : !pto.tile_buf + %c = pto.alloc_tile : !pto.tile_buf + %fma = pto.alloc_tile : !pto.tile_buf + %moved = pto.alloc_tile : !pto.tile_buf + + pto.tfma ins(%a, %b, %c : !pto.tile_buf, !pto.tile_buf, !pto.tile_buf) outs(%fma : !pto.tile_buf) + pto.gmov ins(%fma, %peer : !pto.tile_buf, i64) outs(%moved : !pto.tile_buf) + return + } +} + +// CHECK: #include "jcore/template_asm.hpp" +// CHECK-LABEL: extern "C" void v058_tfma_gmov( +// CHECK: TFMA( +// CHECK: GMOV( diff --git a/tools/check_v058_pto_manifest.py b/tools/check_v058_pto_manifest.py new file mode 100644 index 0000000000..3fb5d726ed --- /dev/null +++ b/tools/check_v058_pto_manifest.py @@ -0,0 +1,453 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + +"""Check PTOAS PTO-op contracts against the LinxISA v0.58.0 PTO manifest. + +PTOAS is an MLIR PTO dialect-to-EmitC compiler, not a Linx scalar assembler. +This check validates all 109 public operations' exact Linx operand roles/arity, +the matching PTOAS ODS argument surface, the explicit v0.57.1-to-v0.58.0 +contract delta, and the bounded dialect-only surface. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import subprocess +from pathlib import Path + + +EXPECTED_LOCK = { + "release": "0.58.0", + "encoding_abi": "pto-isa-0.58.0-mode-function-v1", + "encoding_projection_sha256": "0cad2272ada8f53fc8354e22568099fe8d6bd4b7832c837260cd370b0fc76ffa", + "content_sha256": "355e96045cd3f159c367c75d32b1b2d0a8438cd8df61def2b5d4cd650d103873", + "release_manifest_sha256": "9f9a5c81cb78b5409e88eb8007bb89d145e298377a458f15a6dbb0ee9ff0ceee", + "source_commit": "1c2cb0dcafdbc151357c83e89e7d9460b5d9f401", + "hardware_profile_path": "spec/hardware-conformance-profile.json", + "hardware_profile_id": "pto-hardware-numeric-0.58.0-ieee-v1", + "hardware_profile_sha256": "c4076cf8ac6ebd0c6db8a070b7c290b9928ed7f98c089538ee53ed4644e1531a", + "numeric_vectors_path": "spec/evidence/pto-isa-0580-hardware-numeric-vectors.json", + "numeric_vectors_sha256": "0ad9405b5b7da3803fcf3f803c66ea66918d4363e4342356b1f818804d7f30e8", + "command_forms_path": "spec/catalog/command-forms.json", + "command_forms_sha256": "aaa16ddd046b7c6ee06aedd6444c90da2541c6f3204ef8d12dba34815e96f15b", + "command_forms_count": 99, + "tile_operations_path": "spec/catalog/tile-operations.json", + "tile_operations_sha256": "aa1fa0a5ba07c7f015875025cc93c42f49b8d4313c55ce2cd72eaaa51bdc7d56", + "tile_operation_count": 109, + "release_manifest_path": "spec/release-manifest.json", + "source_repository": "https://github.com/PTO-ISA/pto-spec.git", +} + +DELETED_ACTIVE_NAMES = { + "ACCCVT", + "TADDC", + "TADDSC", + "TALLOC", + "TAXPY", + "TDEINTERLEAVE", + "TFMOD", + "TFMODS", + "TFREE", + "TGATHERB", + "TINTERLEAVE", + "TLRELU", + "TPARTARGMAX", + "TPARTARGMIN", + "TPOP", + "TPRELU", + "TPUSH", + "TRESHAPE", + "TRANDOM", + "TSORT32", + "TSUBC", + "TSUBSC", +} + +def normalize(name: str) -> str: + return name.replace(".", "").replace("_", "").upper() + + +def sha256(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def load_lock(ptoas_root: Path) -> dict: + lock_path = ptoas_root / "tools/pto_isa_v0_58_0_lock.json" + lock = json.loads(lock_path.read_text()) + errors = [] + for key in ("release", "encoding_abi", "encoding_projection_sha256", "content_sha256"): + if lock.get(key) != EXPECTED_LOCK[key]: + errors.append(f"{key}: expected {EXPECTED_LOCK[key]}, got {lock.get(key)}") + release_manifest = lock.get("release_manifest", {}) + for key, expected_key in ( + ("path", "release_manifest_path"), + ("sha256", "release_manifest_sha256"), + ): + if release_manifest.get(key) != EXPECTED_LOCK[expected_key]: + errors.append(f"release_manifest.{key} mismatch") + source = lock.get("source", {}) + for key, expected_key in ( + ("commit", "source_commit"), + ("repository", "source_repository"), + ): + if source.get(key) != EXPECTED_LOCK[expected_key]: + errors.append(f"source.{key} mismatch") + hardware_profile = lock.get("hardware_conformance_profile", {}) + for key, expected_key in ( + ("path", "hardware_profile_path"), + ("profile_id", "hardware_profile_id"), + ("sha256", "hardware_profile_sha256"), + ): + if hardware_profile.get(key) != EXPECTED_LOCK[expected_key]: + errors.append(f"hardware_conformance_profile.{key} mismatch") + numeric_vectors = lock.get("numeric_conformance_vectors", {}) + for key, expected_key in ( + ("path", "numeric_vectors_path"), + ("sha256", "numeric_vectors_sha256"), + ): + if numeric_vectors.get(key) != EXPECTED_LOCK[expected_key]: + errors.append(f"numeric_conformance_vectors.{key} mismatch") + catalogs = lock.get("catalogs", {}) + for catalog, expected_keys in ( + ( + "command_forms", + ("command_forms_path", "command_forms_sha256", "command_forms_count"), + ), + ( + "tile_operations", + ("tile_operations_path", "tile_operations_sha256", "tile_operation_count"), + ), + ): + entry = catalogs.get(catalog, {}) + for key, expected_key in zip(("path", "sha256", "count"), expected_keys): + if entry.get(key) != EXPECTED_LOCK[expected_key]: + errors.append(f"catalogs.{catalog}.{key} mismatch") + if errors: + raise SystemExit("unexpected PTO ISA 0.58.0 lock:\n " + "\n ".join(errors)) + return lock + + +def validate_source_tree(source_root: Path, lock: dict) -> None: + try: + head = subprocess.run( + ["git", "-C", str(source_root), "rev-parse", "HEAD"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + except (OSError, subprocess.CalledProcessError) as error: + raise SystemExit(f"cannot resolve PTO ISA source commit in {source_root}: {error}") + if head != lock["source"]["commit"]: + raise SystemExit( + f"PTO ISA source commit mismatch: expected {lock['source']['commit']}, got {head}" + ) + + pinned_files = ( + lock["catalogs"]["command_forms"], + lock["catalogs"]["tile_operations"], + lock["release_manifest"], + lock["hardware_conformance_profile"], + lock["numeric_conformance_vectors"], + ) + for entry in pinned_files: + path = source_root / entry["path"] + if not path.is_file(): + raise SystemExit(f"missing pinned PTO ISA source file: {path}") + actual = sha256(path) + if actual != entry["sha256"]: + raise SystemExit( + f"PTO ISA source hash mismatch for {entry['path']}: " + f"expected {entry['sha256']}, got {actual}" + ) + + manifest = json.loads((source_root / lock["release_manifest"]["path"]).read_text()) + expected_manifest_fields = { + "release": lock["release"], + "encoding_abi": lock["encoding_abi"], + "encoding_projection_sha256": lock["encoding_projection_sha256"], + "content_sha256": lock["content_sha256"], + } + for key, expected in expected_manifest_fields.items(): + if manifest.get(key) != expected: + raise SystemExit( + f"PTO ISA release manifest {key} mismatch: " + f"expected {expected}, got {manifest.get(key)}" + ) + hardware = manifest.get("hardware_conformance_profile", {}) + for key in ("path", "profile_id", "sha256"): + if hardware.get(key) != lock["hardware_conformance_profile"][key]: + raise SystemExit(f"PTO ISA release manifest hardware profile {key} mismatch") + if hardware.get("evidence") != lock["numeric_conformance_vectors"]["path"]: + raise SystemExit("PTO ISA release manifest hardware numeric evidence path mismatch") + owned_hashes = { + entry.get("path"): entry.get("sha256") + for entry in manifest.get("owned_artifacts", []) + } + numeric_vectors = lock["numeric_conformance_vectors"] + if owned_hashes.get(numeric_vectors["path"]) != numeric_vectors["sha256"]: + raise SystemExit("PTO ISA release manifest hardware numeric evidence hash mismatch") + if ( + manifest.get("catalog_counts", {}).get("tile_operations_total") + != lock["catalogs"]["tile_operations"]["count"] + ): + raise SystemExit("PTO ISA release manifest tile operation count mismatch") + if ( + manifest.get("catalog_counts", {}).get("command_forms") + != lock["catalogs"]["command_forms"]["count"] + ): + raise SystemExit("PTO ISA release manifest command form count mismatch") + + +def load_manifest(linx_root: Path) -> dict[str, dict]: + manifest_path = linx_root / "isa/v0.58/state/pto_ops.json" + manifest = json.loads(manifest_path.read_text()) + if manifest["profile"] != "v0.58" or manifest["operation_count"] != 109: + raise SystemExit(f"unexpected v0.58.0 manifest header in {manifest_path}") + source_lock = manifest.get("source_lock") + if source_lock != "isa/v0.58/pto-spec.lock.json": + raise SystemExit(f"unexpected v0.58.0 source_lock in {manifest_path}: {source_lock}") + operations = manifest["operations"] + names = [entry["name"] for entry in operations] + if len(names) != len(set(names)): + raise SystemExit(f"duplicate operation names in {manifest_path}") + return {entry["name"]: entry for entry in operations} + + +def load_expected_contracts(ptoas_root: Path) -> tuple[dict[str, dict], dict[str, dict]]: + delta_path = ptoas_root / "tools/pto_isa_v0_58_0_operation_contracts.json" + delta = json.loads(delta_path.read_text()) + if delta.get("release") != EXPECTED_LOCK["release"]: + raise SystemExit(f"unexpected release in {delta_path}") + + base_path = ptoas_root / "tools" / delta.get("base_contract", "") + base = json.loads(base_path.read_text()) + base_operations = dict(base.get("operations", {})) + operations = dict(base_operations) + for name in delta.get("removed_public_operations", []): + if operations.pop(name, None) is None: + raise SystemExit(f"missing removed base operation {name} in {base_path}") + for name, contract in delta.get("added_operations", {}).items(): + if name in operations: + raise SystemExit(f"duplicate added operation {name} in {delta_path}") + operations[name] = contract + for name, operands in delta.get("isa_operand_overrides", {}).items(): + if name not in operations: + raise SystemExit(f"operand override references unknown operation {name}") + operations[name] = dict(operations[name]) + operations[name]["isa_operands"] = operands + + expected_count = EXPECTED_LOCK["tile_operation_count"] + if delta.get("public_operation_count") != expected_count or len(operations) != expected_count: + raise SystemExit(f"unexpected public operation count in {delta_path}") + + dialect_only_entries = dict(base.get("dialect_only_operations", {})) + for name in delta.get("moved_to_dialect_only_operations", []): + contract = base_operations.get(name) + if contract is None: + raise SystemExit(f"missing dialect-only migration source {name} in {base_path}") + dialect_only_entries[contract["ptoas_mnemonic"]] = contract["ptoas_arguments"] + dialect_only = { + normalize(mnemonic): { + "ptoas_mnemonic": mnemonic, + "ptoas_arguments": arguments, + } + for mnemonic, arguments in dialect_only_entries.items() + } + if len(dialect_only) != len(dialect_only_entries): + raise SystemExit(f"duplicate dialect-only operation names in {base_path}") + public_mnemonics = { + normalize(entry["ptoas_mnemonic"]) for entry in operations.values() + } + overlap = sorted(public_mnemonics & set(dialect_only)) + if overlap: + raise SystemExit( + f"public/dialect-only operation overlap in {delta_path}: {overlap}" + ) + return operations, dialect_only + + +def load_ptoas_ops(ptoas_root: Path) -> dict[str, dict]: + ods_path = ptoas_root / "include/PTO/IR/PTOOps.td" + if not ods_path.exists(): + ods_path = ptoas_root / "compiler/ptoas/include/PTO/IR/PTOOps.td" + text = ods_path.read_text() + definitions = list( + re.finditer(r'\bdef\s+\w+\s*:\s*PTO_TOp<"([^"]+)"', text) + ) + operations = {} + for index, definition in enumerate(definitions): + mnemonic = definition.group(1) + start = definition.start() + end = definitions[index + 1].start() if index + 1 < len(definitions) else len(text) + body = text[start:end] + match = re.search(r"let\s+arguments\s*=\s*\(ins(.*?)\);", body, re.DOTALL) + arguments = tuple(re.findall(r"\$([A-Za-z0-9_]+)", match.group(1))) if match else () + key = normalize(mnemonic) + if key in operations: + raise SystemExit(f"duplicate normalized PTOAS mnemonic in {ods_path}: {mnemonic}") + operations[key] = {"mnemonic": mnemonic, "arguments": arguments} + return operations + + +def validate_linx_target_surface(ptoas_root: Path) -> None: + cli_text = (ptoas_root / "tools/ptoas/ptoas.cpp").read_text() + lowering_text = (ptoas_root / "lib/PTO/Transforms/PTOToEmitC.cpp").read_text() + errors = [] + for token in ( + 'value_desc("a3|a5|linx")', + 'PTOParserTargetArch::Linx', + 'PTOArch::Linx', + '"jcore/template_asm.hpp"', + ): + if token not in cli_text and token not in lowering_text: + errors.append(f"missing Linx target implementation token: {token}") + delta = json.loads( + (ptoas_root / "tools/pto_isa_v0_58_0_operation_contracts.json").read_text() + ) + base = json.loads( + (ptoas_root / "tools" / delta["base_contract"]).read_text() + ) + for name in delta["removed_public_operations"]: + contract = base["operations"][name] + mnemonic = f'"pto.{contract["ptoas_mnemonic"]}"' + if mnemonic not in cli_text: + errors.append( + f"Linx target boundary does not explicitly reject dialect-only {mnemonic}" + ) + if 'ValueRange{dst, peerTid, src}' not in lowering_text: + errors.append("GMOV lowering must match Linx-TileOP-API order (dst, peer_tid, src)") + if 'ValueRange{dst, src0, src1, src2}' not in lowering_text: + errors.append("TFMA lowering must match Linx-TileOP-API order (dst, src0, src1, src2)") + if errors: + raise SystemExit("invalid PTOAS Linx v0.58 target surface:\n " + "\n ".join(errors)) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--ptoas-root", type=Path, default=Path(__file__).resolve().parents[1]) + parser.add_argument("--linx-root", type=Path) + parser.add_argument( + "--pto-spec-root", + type=Path, + help="optionally verify every pinned identity against a PTO-ISA/pto-spec checkout", + ) + args = parser.parse_args() + + ptoas_root = args.ptoas_root.resolve() + lock = load_lock(ptoas_root) + if args.pto_spec_root is not None: + validate_source_tree(args.pto_spec_root.resolve(), lock) + expected_public, expected_dialect_only = load_expected_contracts(ptoas_root) + validate_linx_target_surface(ptoas_root) + ptoas_ops = load_ptoas_ops(ptoas_root) + + deleted_present = sorted( + name + for name in DELETED_ACTIVE_NAMES + if normalize(name) in ptoas_ops and normalize(name) not in expected_dialect_only + ) + boundary_error = bool(deleted_present) + if deleted_present: + print("PTOAS has deleted PTO ISA 0.58.0 names active in the dialect:") + for name in deleted_present: + print(f" - {name}") + + expected_all = { + normalize(contract["ptoas_mnemonic"]) + for contract in expected_public.values() + } | set(expected_dialect_only) + missing_ops = sorted(expected_all - set(ptoas_ops)) + undeclared_ops = sorted(set(ptoas_ops) - expected_all) + if missing_ops or undeclared_ops: + boundary_error = True + print("PTOAS public/dialect-only operation boundary mismatch:") + for name in missing_ops: + print(f" - missing declared operation: {name}") + for name in undeclared_ops: + print(f" - undeclared dialect-only operation: {name}") + if boundary_error: + return 1 + + contract_errors = [] + for name, contract in expected_public.items(): + actual = ptoas_ops[normalize(contract["ptoas_mnemonic"])]["arguments"] + expected = tuple(contract["ptoas_arguments"]) + if actual != expected: + contract_errors.append(f"{name}: expected PTOAS arguments {expected}, got {actual}") + for key, contract in expected_dialect_only.items(): + actual = ptoas_ops[key]["arguments"] + expected = tuple(contract["ptoas_arguments"]) + if actual != expected: + contract_errors.append( + f"dialect-only {contract['ptoas_mnemonic']}: " + f"expected PTOAS arguments {expected}, got {actual}" + ) + if contract_errors: + print("PTOAS has incorrect PTO ISA 0.58.0 operation roles/arity:") + for error in contract_errors: + print(f" - {error}") + return 1 + + if args.linx_root is None: + print( + "PTOAS v0.58.0 PTO lock/dialect check OK: all 109 public " + f"operation argument contracts and {len(expected_dialect_only)} explicit " + "dialect-only contracts match; hardware numeric profile/vectors are " + "identity metadata only (execution conformance not evaluated)" + ) + return 0 + + manifest = load_manifest(args.linx_root.resolve()) + + expected_names = set(expected_public) + actual_names = set(manifest) + if actual_names != expected_names: + print("LinxISA v0.58.0 public operation boundary mismatch:") + for name in sorted(expected_names - actual_names): + print(f" - missing manifest operation: {name}") + for name in sorted(actual_names - expected_names): + print(f" - unexpected manifest operation: {name}") + return 1 + + role_errors = [] + for name, contract in expected_public.items(): + entry = manifest[name] + actual = tuple( + (operand.get("field"), operand.get("role")) + for operand in entry.get("operands", []) + ) + expected = tuple( + (operand[0], operand[1]) + for operand in contract["isa_operands"] + ) + if actual != expected: + role_errors.append( + f"{name}: expected ISA operands {expected}, got {actual}" + ) + if role_errors: + print("LinxISA v0.58.0 manifest role/arity mismatch:") + for error in role_errors: + print(f" - {error}") + return 1 + + print( + "PTOAS v0.58.0 PTO manifest contract check OK: " + f"all {len(manifest)} public operations match exact Linx roles/arity; " + f"{len(expected_dialect_only)} dialect-only operations are explicitly bounded; " + "hardware numeric profile/vectors are identity metadata only " + "(execution conformance not evaluated)" + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/pto_isa_v0_58_0_lock.json b/tools/pto_isa_v0_58_0_lock.json new file mode 100644 index 0000000000..e3d7b94220 --- /dev/null +++ b/tools/pto_isa_v0_58_0_lock.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://docs.openclaw.ai/schemas/linxisa/pto_spec_lock.v1.json", + "catalogs": { + "command_forms": { + "path": "spec/catalog/command-forms.json", + "sha256": "aaa16ddd046b7c6ee06aedd6444c90da2541c6f3204ef8d12dba34815e96f15b", + "count": 99 + }, + "scalar_forms": { + "path": "spec/catalog/scalar-forms.json", + "sha256": "f6f753034d8c6538c6eeb5320005184e2267152f350748e2442254f1b30c9e5f", + "count": 474 + }, + "tile_operations": { + "path": "spec/catalog/tile-operations.json", + "sha256": "aa1fa0a5ba07c7f015875025cc93c42f49b8d4313c55ce2cd72eaaa51bdc7d56", + "count": 109 + }, + "linx_vector_reservations": { + "path": "spec/catalog/linx-vector-reservations.json", + "sha256": "1d3c666f2d2c6e5b8f3546cfaee55dd68ae10b29168bd02cdca63f99accfecfd", + "count": 5 + } + }, + "content_sha256": "355e96045cd3f159c367c75d32b1b2d0a8438cd8df61def2b5d4cd650d103873", + "encoding_abi": "pto-isa-0.58.0-mode-function-v1", + "encoding_projection_sha256": "0cad2272ada8f53fc8354e22568099fe8d6bd4b7832c837260cd370b0fc76ffa", + "hardware_conformance_profile": { + "path": "spec/hardware-conformance-profile.json", + "profile_id": "pto-hardware-numeric-0.58.0-ieee-v1", + "sha256": "c4076cf8ac6ebd0c6db8a070b7c290b9928ed7f98c089538ee53ed4644e1531a" + }, + "numeric_conformance_vectors": { + "path": "spec/evidence/pto-isa-0580-hardware-numeric-vectors.json", + "sha256": "0ad9405b5b7da3803fcf3f803c66ea66918d4363e4342356b1f818804d7f30e8" + }, + "release": "0.58.0", + "release_manifest": { + "path": "spec/release-manifest.json", + "sha256": "9f9a5c81cb78b5409e88eb8007bb89d145e298377a458f15a6dbb0ee9ff0ceee" + }, + "source": { + "commit": "1c2cb0dcafdbc151357c83e89e7d9460b5d9f401", + "tree": "2c0685f8d82ca289aeda2a33989ff200aeb9d81a", + "repository": "https://github.com/PTO-ISA/pto-spec.git" + } +} diff --git a/tools/pto_isa_v0_58_0_operation_contracts.json b/tools/pto_isa_v0_58_0_operation_contracts.json new file mode 100644 index 0000000000..1f81e98f4b --- /dev/null +++ b/tools/pto_isa_v0_58_0_operation_contracts.json @@ -0,0 +1,76 @@ +{ + "release": "0.58.0", + "base_contract": "pto_isa_v0_57_1_operation_contracts.json", + "public_operation_count": 109, + "removed_public_operations": [ + "ACCCVT", + "TALLOC", + "TAXPY", + "TDEINTERLEAVE", + "TFREE", + "TGATHERB", + "TINTERLEAVE", + "TPARTARGMAX", + "TPARTARGMIN", + "TPOP", + "TPRELU", + "TPUSH", + "TRESHAPE" + ], + "moved_to_dialect_only_operations": [ + "ACCCVT", + "TALLOC", + "TAXPY", + "TDEINTERLEAVE", + "TFREE", + "TGATHERB", + "TINTERLEAVE", + "TPARTARGMAX", + "TPARTARGMIN", + "TPOP", + "TPRELU", + "TPUSH", + "TRESHAPE" + ], + "added_operations": { + "GMOV": { + "ptoas_mnemonic": "gmov", + "ptoas_arguments": ["src", "peer_tid", "dst"], + "isa_operands": [ + ["destination0", "destination"], + ["source0", "resolved-peer-source"], + ["scalar0", "peer-tid"] + ] + }, + "TFMA": { + "ptoas_mnemonic": "tfma", + "ptoas_arguments": ["src0", "src1", "src2", "dst"], + "isa_operands": [ + ["destination0", "destination"], + ["source0", "multiplicand-left"], + ["source1", "multiplicand-right"], + ["source2", "addend"] + ] + } + }, + "isa_operand_overrides": { + "TCVT": [["destination0", "destination"], ["source0", "source"], ["numeric_control", "rounding-and-saturation"]], + "TDEQUANT": [["destination0", "destination"], ["source0", "source"], ["scalar0", "scale"], ["scalar1", "zero-point"], ["numeric_control", "rounding-and-saturation"]], + "TGEMV": [["destination0", "destination"], ["source0", "matrix"], ["source1", "vector"]], + "TGEMV_ACC": [["destination0", "destination"], ["source0", "accumulator"], ["source1", "matrix"], ["source2", "vector"]], + "TGEMV_BIAS": [["destination0", "destination"], ["source0", "matrix"], ["source1", "vector"], ["source2", "bias"]], + "TGEMV_MX": [["destination0", "destination"], ["source0", "matrix"], ["source1", "row-scale"], ["source2", "vector"], ["source3", "column-scale"]], + "TGEMV_MX_ACC": [["destination0", "destination"], ["source0", "accumulator"], ["source1", "matrix"], ["source2", "row-scale"], ["source3", "vector"], ["source4", "column-scale"]], + "TGEMV_MX_BIAS": [["destination0", "destination"], ["source0", "matrix"], ["source1", "row-scale"], ["source2", "vector"], ["source3", "column-scale"], ["source4", "bias"]], + "TLOAD": [["destination0", "destination"], ["address", "base-address"], ["scalar0", "row-stride-elements"]], + "TMATMUL": [["destination0", "destination"], ["source0", "left"], ["source1", "right"]], + "TMATMUL_ACC": [["destination0", "destination"], ["source0", "accumulator"], ["source1", "left"], ["source2", "right"]], + "TMATMUL_BIAS": [["destination0", "destination"], ["source0", "left"], ["source1", "right"], ["source2", "bias"]], + "TMATMUL_MX": [["destination0", "destination"], ["source0", "left"], ["source1", "row-scale"], ["source2", "right"], ["source3", "column-scale"]], + "TMATMUL_MX_ACC": [["destination0", "destination"], ["source0", "accumulator"], ["source1", "left"], ["source2", "row-scale"], ["source3", "right"], ["source4", "column-scale"]], + "TMATMUL_MX_BIAS": [["destination0", "destination"], ["source0", "left"], ["source1", "row-scale"], ["source2", "right"], ["source3", "column-scale"], ["source4", "bias"]], + "TQUANT": [["destination0", "destination"], ["source0", "source"], ["scalar0", "scale"], ["scalar1", "zero-point"], ["numeric_control", "rounding-and-saturation"]], + "TSORT": [["destination0", "destination"], ["destination1", "original-indices-u32"], ["source0", "source"], ["sort_width", "sort-width"], ["flag0", "descending"]], + "TSTORE": [["address", "base-address"], ["scalar0", "row-stride-elements"], ["source0", "source"]] + } +} diff --git a/tools/ptoas/ptoas.cpp b/tools/ptoas/ptoas.cpp index 881d6adff1..2863350929 100644 --- a/tools/ptoas/ptoas.cpp +++ b/tools/ptoas/ptoas.cpp @@ -225,8 +225,8 @@ static llvm::cl::opt emitMlirIR( static llvm::cl::opt ptoTargetArch( "pto-arch", - llvm::cl::desc("Target Ascend architecture for codegen: a3 or a5 (default: a3)"), - llvm::cl::value_desc("a3|a5"), + llvm::cl::desc("Target architecture for codegen: a3, a5, or linx (default: a3)"), + llvm::cl::value_desc("a3|a5|linx"), llvm::cl::init("a3")); static llvm::cl::opt ptoBuildLevel( @@ -241,6 +241,33 @@ enum class PTOBuildLevel { Level3, }; +static LogicalResult validateLinxOperationBoundary(ModuleOp module) { + if (!pto::isTargetArchLinx(module)) + return success(); + + static constexpr llvm::StringLiteral linxDialectOnlyOps[] = { + "pto.acccvt", "pto.talloc", "pto.taxpy", + "pto.tdeinterleave", "pto.tfree", "pto.tgatherb", + "pto.tinterleave", "pto.tpartargmax", "pto.tpartargmin", + "pto.tpop", "pto.tprelu", "pto.tpush", + "pto.treshape", + }; + + llvm::SmallVector invalid; + module.walk([&](Operation *op) { + StringRef name = op->getName().getStringRef(); + if (llvm::is_contained(linxDialectOnlyOps, name)) + invalid.push_back(op); + }); + for (Operation *op : invalid) { + StringRef name = op->getName().getStringRef(); + op->emitError() << "'" << name + << "' op is dialect-only and not part of the active Linx " + "PTO ISA v0.58.0 target"; + } + return invalid.empty() ? success() : failure(); +} + static PTOBuildLevel defaultBuildLevel() { return PTOBuildLevel::Level2; } @@ -1000,16 +1027,16 @@ int main(int argc, char **argv) { std::string arch = normalizeArch(ptoTargetArch); if (cliArchSpecified) { - if (arch != "a3" && arch != "a5") { + if (arch != "a3" && arch != "a5" && arch != "linx") { llvm::errs() << "Error: invalid --pto-arch='" << ptoTargetArch - << "'. Expected 'a3' or 'a5'.\n"; + << "'. Expected 'a3', 'a5', or 'linx'.\n"; return 1; } } else if (!isPTOBC) { if (auto detectedArch = detectTextualModuleArch(buf)) arch = *detectedArch; } - if (arch != "a3" && arch != "a5") + if (arch != "a3" && arch != "a5" && arch != "linx") arch = "a3"; if (isPTOBC) { @@ -1033,9 +1060,12 @@ int main(int argc, char **argv) { // Parse textual MLIR (.pto). llvm::SourceMgr sourceMgr; sourceMgr.AddNewSourceBuffer(std::move(*fileOrErr), llvm::SMLoc()); - pto::ScopedPTOParserTargetArch scopedParserArch( - &context, arch == "a5" ? pto::PTOParserTargetArch::A5 - : pto::PTOParserTargetArch::A3); + pto::PTOParserTargetArch parserArch = pto::PTOParserTargetArch::A3; + if (arch == "a5") + parserArch = pto::PTOParserTargetArch::A5; + else if (arch == "linx") + parserArch = pto::PTOParserTargetArch::Linx; + pto::ScopedPTOParserTargetArch scopedParserArch(&context, parserArch); module = parseSourceFile(sourceMgr, &context); if (!module) { llvm::errs() << "Error: Failed to parse MLIR.\n"; @@ -1051,6 +1081,9 @@ int main(int argc, char **argv) { mlir::StringAttr::get(&context, arch)); } + if (failed(validateLinxOperationBoundary(*module))) + return 1; + PTOBuildLevel effectiveLevel = defaultBuildLevel(); if (!parseBuildLevel(ptoBuildLevel, effectiveLevel)) { llvm::errs() << "Error: invalid --pto-level='" << ptoBuildLevel @@ -1211,8 +1244,10 @@ int main(int argc, char **argv) { pm.addPass(createCSEPass()); if (arch == "a3") { pm.addPass(pto::createEmitPTOManualPass(pto::PTOArch::A3)); - } else { + } else if (arch == "a5") { pm.addPass(pto::createEmitPTOManualPass(pto::PTOArch::A5)); + } else { + pm.addPass(pto::createEmitPTOManualPass(pto::PTOArch::Linx)); } pm.addPass(emitc::createFormExpressionsPass()); pm.addPass(mlir::createCSEPass()); diff --git a/tools/ptobc/generated/ptobc_opcodes_v0.h b/tools/ptobc/generated/ptobc_opcodes_v0.h index 2575b78b51..457938d8e7 100644 --- a/tools/ptobc/generated/ptobc_opcodes_v0.h +++ b/tools/ptobc/generated/ptobc_opcodes_v0.h @@ -233,6 +233,8 @@ inline constexpr OpInfo kOpTable[] = { {0x109E, "pto.mgather_cas", 0, 0x00, 0x00, 5, 0, 0, 0x00}, {0x109F, "pto.mscatter_mask", 0, 0x00, 0x00, 4, 0, 0, 0x00}, {0x10A0, "pto.tsort", 0, 0x00, 0x00, 3, 0, 0, 0x00}, + {0x10A1, "pto.gmov", 0, 0x00, 0x00, 3, 0, 0, 0x00}, + {0x10A2, "pto.tfma", 0, 0x00, 0x00, 4, 0, 0, 0x00}, {0x2000, "arith.addi", 0, 0x01, 0x00, 2, 1, 0, 0x00}, {0x2001, "arith.ceildivsi", 0, 0x01, 0x00, 2, 1, 0, 0x00}, {0x2002, "arith.cmpi", 0, 0x01, 0x00, 2, 1, 0, 0x01}, @@ -437,6 +439,8 @@ inline std::optional lookupOpcodeByName(llvm::StringRef name) { .Case("pto.comm.treduce", 0x1099) .Case("pto.tpartargmax", 0x109A) .Case("pto.tpartargmin", 0x109B) + .Case("pto.gmov", 0x10A1) + .Case("pto.tfma", 0x10A2) .Case("scf.for", 0x4000) .Case("scf.if", 0x4001) .Case("scf.yield", 0x4002) @@ -625,6 +629,8 @@ inline std::optional lookupOpcodeAndVariantByFullName(llvm::St .Case("pto.comm.treduce", OpcodeAndVariant{0x1099, 0, 0}) .Case("pto.tpartargmax", OpcodeAndVariant{0x109A, 0, 0}) .Case("pto.tpartargmin", OpcodeAndVariant{0x109B, 0, 0}) + .Case("pto.gmov", OpcodeAndVariant{0x10A1, 0, 0}) + .Case("pto.tfma", OpcodeAndVariant{0x10A2, 0, 0}) .Case("scf.for", OpcodeAndVariant{0x4000, 0, 0}) .Case("scf.if", OpcodeAndVariant{0x4001, 0, 0}) .Case("scf.yield", OpcodeAndVariant{0x4002, 0, 0}) diff --git a/tools/ptobc/tests/CMakeLists.txt b/tools/ptobc/tests/CMakeLists.txt index a733d5087d..76f543e865 100644 --- a/tools/ptobc/tests/CMakeLists.txt +++ b/tools/ptobc/tests/CMakeLists.txt @@ -36,12 +36,12 @@ add_test(NAME ptobc_opcode_coverage_check ${CMAKE_SOURCE_DIR}/tools/ptobc/generated/ptobc_opcodes_v0.h ) -add_test(NAME ptobc_v0571_ops_v0_encode +add_test(NAME ptobc_v0580_ops_v0_encode COMMAND ${CMAKE_COMMAND} -E env PTOBC_BIN=$ TEST_INPUT_DIR=${CMAKE_SOURCE_DIR}/test/lit/pto - OUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/ptobc_v0571_ops_out - ${CMAKE_CURRENT_LIST_DIR}/v0571_ops_v0_encode.sh + OUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/ptobc_v0580_ops_out + bash ${CMAKE_CURRENT_LIST_DIR}/v0580_ops_v0_encode.sh ) add_test(NAME ptobc_trowexpandsub_v0_encode diff --git a/tools/ptobc/tests/opcode_coverage_check.py b/tools/ptobc/tests/opcode_coverage_check.py index ac93530f0e..5254f64f26 100755 --- a/tools/ptobc/tests/opcode_coverage_check.py +++ b/tools/ptobc/tests/opcode_coverage_check.py @@ -53,12 +53,19 @@ "pto.tsort": (0x10A0, 3), } +NEW_V0580_OPCODE_ASSIGNMENTS = { + "pto.gmov": (0x10A1, 3), + "pto.tfma": (0x10A2, 4), +} + EXACT_ARITY = { "pto.tprefetch": 2, **{name: arity for name, (_, arity) in NEW_V0571_OPCODE_ASSIGNMENTS.items()}, + **{name: arity for name, (_, arity) in NEW_V0580_OPCODE_ASSIGNMENTS.items()}, } PRE_V0571_MAX_PTO_OPCODE = 0x109B +PRE_V0580_MAX_PTO_OPCODE = 0x10A0 def parse_td_mnemonics(td_path: Path): @@ -170,6 +177,15 @@ def check_header_contract(h_path: Path): for name, (opcode, arity) in NEW_V0571_OPCODE_ASSIGNMENTS.items(): if opcode <= PRE_V0571_MAX_PTO_OPCODE: errors.append(f"new operation {name} does not use a fresh opcode") + if table_by_name.get(name) != (opcode, arity): + errors.append( + f"{name}: expected opcode/arity {(opcode, arity)}, " + f"got {table_by_name.get(name)}" + ) + + for name, (opcode, arity) in NEW_V0580_OPCODE_ASSIGNMENTS.items(): + if opcode <= PRE_V0580_MAX_PTO_OPCODE: + errors.append(f"new operation {name} does not use a fresh opcode") if table_by_name.get(name) != (opcode, arity): errors.append( f"{name} table contract must be opcode=0x{opcode:04X}, arity={arity}" diff --git a/tools/ptobc/tests/v0580_ops_v0_encode.sh b/tools/ptobc/tests/v0580_ops_v0_encode.sh new file mode 100644 index 0000000000..aed9f471e7 --- /dev/null +++ b/tools/ptobc/tests/v0580_ops_v0_encode.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + +set -euo pipefail + +: "${PTOBC_BIN:?PTOBC_BIN not set}" +: "${TEST_INPUT_DIR:?TEST_INPUT_DIR not set}" + +OUT_DIR=${OUT_DIR:-"${PWD}/ptobc_v0580_ops_out"} +mkdir -p "${OUT_DIR}" + +input="${TEST_INPUT_DIR}/v058_tfma_gmov_emitc.pto" +bytecode="${OUT_DIR}/v058_tfma_gmov_emitc.ptobc" +decoded="${OUT_DIR}/v058_tfma_gmov_emitc.roundtrip.pto" +"${PTOBC_BIN}" encode "${input}" -o "${bytecode}" +"${PTOBC_BIN}" decode "${bytecode}" -o "${decoded}" + +grep -F "pto.tfma ins(" "${decoded}" >/dev/null +grep -F "pto.gmov ins(" "${decoded}" >/dev/null From 820fdcede77b538d5531707b51cbebd22cf9434a Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Tue, 11 Aug 2026 19:49:31 +0800 Subject: [PATCH 02/32] build: complete LLVM 23 migration for v0.58 --- .github/workflows/build_wheel.yml | 33 +- .github/workflows/build_wheel_mac.yml | 31 +- .github/workflows/ci.yml | 12 +- CMakeLists.txt | 7 +- README.md | 25 +- lib/PTO/Transforms/PTOToEmitC.cpp | 297 ++++++++++++------ test/lit/pto/async_put_get_emitc.pto | 6 +- ...edge_nested_same_pipe_prune_regression.pto | 12 +- test/lit/pto/comm_collective_emitc.pto | 14 +- test/lit/pto/comm_p2p_emitc.pto | 20 +- test/lit/pto/eventid_array_dyn_sync.pto | 13 +- test/lit/pto/eventid_array_get_set_get.pto | 8 +- test/lit/pto/eventid_array_no_cse.pto | 4 +- .../lit/pto/issue428_cube_sync_regression.pto | 66 ++-- ...p_if_else_loop_carried_sync_regression.pto | 16 +- ..._else_loop_carried_sync_regression_gss.pto | 4 +- ..._nested_loop_same_pipe_pair_regression.pto | 4 +- ...ted_loop_same_pipe_pair_regression_gss.pto | 2 +- ...ssue533_loop_zero_trip_sync_regression.pto | 2 +- ...533_loop_zero_trip_sync_regression_gss.pto | 2 +- ...ue564_k_loop_mte1_mte2_wait_regression.pto | 2 +- ...e622_v_mte2_eventid_overlap_reproducer.pto | 4 +- .../kernel_kind_vector_scf_while_emitc.pto | 17 +- test/lit/pto/local_array_1d_emitc.pto | 3 +- test/lit/pto/local_array_2d_emitc.pto | 3 +- test/lit/pto/local_array_get_rvalue_emitc.pto | 4 +- .../lit/pto/syncfinder_zero_loop_if_probe.pto | 4 +- .../pto/syncfinder_zero_loop_if_probe_gss.pto | 4 +- test/lit/pto/tci_i16_emitc.pto | 2 +- test/lit/pto/tci_ui32_emitc.pto | 2 +- test/lit/pto/tcvt_e2e.pto | 8 +- test/lit/pto/tcvt_emitc.pto | 8 +- test/lit/pto/tprint_alloc_tile_no_rebind.pto | 2 +- test/lit/pto/tpush_tpop_emitc.pto | 6 +- ...h_tpop_globaltensor_split_half_slot_a3.pto | 2 +- test/lit/pto/tsort_emitc.pto | 4 +- 36 files changed, 395 insertions(+), 258 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 70fa021fda..6d01163c3b 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -24,7 +24,8 @@ permissions: contents: write env: - LLVM_TAG: llvmorg-19.1.7 + LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git + LLVM_COMMIT: be1fc2451936a713e2a15620d5e7f8fe0a73688e jobs: build_wheel: @@ -59,10 +60,15 @@ jobs: run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then EXPECTED_RELEASE_VERSION="$(${PY_PATH}/bin/python .github/scripts/compute_ptoas_version.py --mode release)" - PTOAS_VERSION="${GITHUB_REF_NAME#v}" - if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then - echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 - exit 1 + if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.0" ]; then + ${PY_PATH}/bin/python tools/check_v058_pto_manifest.py --ptoas-root . + PTOAS_VERSION="${EXPECTED_RELEASE_VERSION}" + else + PTOAS_VERSION="${GITHUB_REF_NAME#v}" + if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then + echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 + exit 1 + fi fi else PTOAS_VERSION="$(${PY_PATH}/bin/python .github/scripts/compute_ptoas_version.py --mode dev)" @@ -95,7 +101,7 @@ jobs: uses: actions/cache/restore@v4 with: path: /llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_TAG }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v2 + key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v3 - name: Prepare LLVM source run: | @@ -103,10 +109,12 @@ jobs: cd $LLVM_SOURCE_DIR if [ ! -d .git ]; then git init - git remote add origin https://github.com/llvm/llvm-project.git + git remote add origin ${{ env.LLVM_REPOSITORY }} fi - git fetch --depth 1 origin tag ${{ env.LLVM_TAG }} - git checkout ${{ env.LLVM_TAG }} + git remote set-url origin ${{ env.LLVM_REPOSITORY }} + git fetch --depth 1 origin ${{ env.LLVM_COMMIT }} + git checkout --detach ${{ env.LLVM_COMMIT }} + test "$(git rev-parse HEAD)" = "${{ env.LLVM_COMMIT }}" - name: Warn when default-branch cache is missing for PR/release runs if: steps.cache-llvm.outputs.cache-hit != 'true' && (github.event_name == 'pull_request' || github.event_name == 'release') @@ -114,7 +122,7 @@ jobs: echo "LLVM cache miss while running on PR/release." echo "PR/release runs are cache-consumers only and should reuse cache generated on the default branch." echo "Please run this workflow on the default branch first to populate cache key:" - echo "llvm-${{ env.LLVM_TAG }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v2" + echo "llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v3" - name: Build LLVM/MLIR if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -137,7 +145,7 @@ jobs: uses: actions/cache/save@v4 with: path: /llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_TAG }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v2 + key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v3 - name: Build PTOAS run: | @@ -154,6 +162,7 @@ jobs: -Dpybind11_DIR=$(${PY_PATH}/bin/python -m pybind11 --cmakedir) \ -DMLIR_PYTHON_PACKAGE_DIR=${LLVM_BUILD_DIR}/tools/mlir/python_packages/mlir_core \ -DPTOAS_RELEASE_VERSION_OVERRIDE=${PTOAS_VERSION} \ + -DPTOAS_ENABLE_WERROR=OFF \ -DCMAKE_INSTALL_PREFIX=${PTO_INSTALL_DIR} \ -DCMAKE_BUILD_TYPE=Release ninja -C build @@ -287,7 +296,7 @@ jobs: bump_base_version: name: Bump base version after release - if: github.event_name == 'release' && github.event.action == 'released' + if: ${{ github.event_name == 'release' && github.event.action == 'released' && !startsWith(github.event.release.tag_name, 'linxisa-') }} needs: upload_release_assets runs-on: ubuntu-latest diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index 1f39b78dd7..13b00fa38d 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -23,7 +23,8 @@ permissions: contents: write env: - LLVM_TAG: llvmorg-19.1.7 + LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git + LLVM_COMMIT: be1fc2451936a713e2a15620d5e7f8fe0a73688e jobs: build_wheel: @@ -65,10 +66,15 @@ jobs: run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then EXPECTED_RELEASE_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode release)" - PTOAS_VERSION="${GITHUB_REF_NAME#v}" - if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then - echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 - exit 1 + if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.0" ]; then + python tools/check_v058_pto_manifest.py --ptoas-root . + PTOAS_VERSION="${EXPECTED_RELEASE_VERSION}" + else + PTOAS_VERSION="${GITHUB_REF_NAME#v}" + if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then + echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 + exit 1 + fi fi else PTOAS_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode dev)" @@ -99,7 +105,7 @@ jobs: uses: actions/cache/restore@v4 with: path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_TAG }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v1 + key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v2 - name: Prepare LLVM source run: | @@ -107,10 +113,12 @@ jobs: cd $LLVM_SOURCE_DIR if [ ! -d .git ]; then git init - git remote add origin https://github.com/llvm/llvm-project.git + git remote add origin ${{ env.LLVM_REPOSITORY }} fi - git fetch --depth 1 origin tag ${{ env.LLVM_TAG }} - git checkout ${{ env.LLVM_TAG }} + git remote set-url origin ${{ env.LLVM_REPOSITORY }} + git fetch --depth 1 origin ${{ env.LLVM_COMMIT }} + git checkout --detach ${{ env.LLVM_COMMIT }} + test "$(git rev-parse HEAD)" = "${{ env.LLVM_COMMIT }}" - name: Warn when default-branch cache is missing for PR/release runs if: steps.cache-llvm.outputs.cache-hit != 'true' && (github.event_name == 'pull_request' || github.event_name == 'release') @@ -118,7 +126,7 @@ jobs: echo "LLVM cache miss while running on PR/release." echo "PR/release runs are cache-consumers only and should reuse cache generated on the default branch." echo "Please run this workflow on the default branch first to populate cache key:" - echo "llvm-${{ env.LLVM_TAG }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v1" + echo "llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v2" - name: Build LLVM/MLIR if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -140,7 +148,7 @@ jobs: uses: actions/cache/save@v4 with: path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_TAG }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v1 + key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v2 - name: Build PTOAS run: | @@ -156,6 +164,7 @@ jobs: -Dpybind11_DIR=$(python -m pybind11 --cmakedir) \ -DMLIR_PYTHON_PACKAGE_DIR=${LLVM_BUILD_DIR}/tools/mlir/python_packages/mlir_core \ -DPTOAS_RELEASE_VERSION_OVERRIDE=${PTOAS_VERSION} \ + -DPTOAS_ENABLE_WERROR=OFF \ -DCMAKE_INSTALL_PREFIX=${PTO_INSTALL_DIR} \ -DCMAKE_BUILD_TYPE=Release ninja -C build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 952aa0acee..fbf165f5cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,7 +93,8 @@ jobs: build-and-test: runs-on: ubuntu-22.04 env: - LLVM_COMMIT: cd708029e0b2869e80abe31ddb175f7c35361f90 + LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git + LLVM_COMMIT: be1fc2451936a713e2a15620d5e7f8fe0a73688e LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core @@ -154,11 +155,13 @@ jobs: # 如果 restore 只带来了 build-shared,这里补一个 git repo if [ ! -d .git ]; then git init - git remote add origin https://github.com/llvm/llvm-project.git + git remote add origin "${LLVM_REPOSITORY}" fi - git fetch --depth 1 origin tag llvmorg-19.1.7 - git checkout "${LLVM_COMMIT}" + git remote set-url origin "${LLVM_REPOSITORY}" + git fetch --depth 1 origin "${LLVM_COMMIT}" + git checkout --detach "${LLVM_COMMIT}" + test "$(git rev-parse HEAD)" = "${LLVM_COMMIT}" - name: Build LLVM/MLIR (only if cache miss) if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -195,6 +198,7 @@ jobs: -Dpybind11_DIR="${PYBIND11_CMAKE_DIR}" \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DMLIR_PYTHON_PACKAGE_DIR="${LLVM_DIR}/tools/mlir/python_packages/mlir_core" \ + -DPTOAS_ENABLE_WERROR=OFF \ -DCMAKE_INSTALL_PREFIX="${PTO_INSTALL_DIR}" \ -DCMAKE_BUILD_TYPE=Release ninja -C build ptoas diff --git a/CMakeLists.txt b/CMakeLists.txt index 14b7fea43e..3685ae4529 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,7 +39,7 @@ endif() message(STATUS "PTOAS CLI version: ${PTOAS_CLI_VERSION}") # ========================================================= -# [新增] 强制设置 C++17 标准 (LLVM 19 必需) +# PTOAS uses the C++17 surface required by the governed LinxISA LLVM toolchain. # ========================================================= set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) @@ -76,7 +76,10 @@ include(AddMLIR) # ========================================================= # Keep warning policy in CMake so CI, wheel, and external build entrypoints # all enforce the same diagnostics contract. -option(PTOAS_ENABLE_WERROR "Treat PTOAS warnings as errors" ON) +# LLVM 23 deprecates the generic builder surface used across the existing +# lowering code. Keep warnings visible, but do not make downstream LLVM API +# deprecations fatal while the migration is completed incrementally. +option(PTOAS_ENABLE_WERROR "Treat PTOAS warnings as errors" OFF) if(PTOAS_ENABLE_WERROR) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang|AppleClang") add_compile_options($<$:-Werror>) diff --git a/README.md b/README.md index 3738ec35d8..d9007c9603 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ ## 1. 项目简介 (Introduction) -**ptoas** (`ptoas`) 是一个基于 **LLVM/MLIR (llvmorg-19.1.7)***(Commit cd708029e0b2869e80abe31ddb175f7c35361f90)* 框架构建的专用编译器工具链,专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 +**ptoas** (`ptoas`) 是一个基于 LinxISA 受控 **LLVM/MLIR** +(release `linxisa-v0.58.0`,commit +`be1fc2451936a713e2a15620d5e7f8fe0a73688e`)构建的专用编译器工具链, +专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 作为连接上层 AI 框架与底层各类NPU/GPGPU/CPU硬件,`ptoas` 采用 **Out-of-Tree** 架构构建,提供了完整的 C++ 与 Python 接口,主要职责包括: @@ -37,7 +40,9 @@ PTOAS/ ## 3. 构建指南 (Build Instructions) -⚠️ **重要提示**:本项目严格依赖 **LLVM llvmorg-19.1.7** 版本。 +⚠️ **重要提示**:本项目严格依赖 LinxISA LLVM +`be1fc2451936a713e2a15620d5e7f8fe0a73688e`。该 SHA 对应 +`linxisa-v0.58.0`,不要替换为同名上游 LLVM tag。 ### 3.0 环境变量配置 (Configuration) @@ -83,18 +88,17 @@ python3 -m pip install pybind11==2.12.0 numpy ### 3.2 第一步:构建 LLVM/MLIR (Dependency) -我们需要下载 LLVM 源码,切换到 `llvmorg-19.1.7` 标签,并以**动态库 (Shared Libs)** 模式编译,以确保 Python Binding 的正确链接。 +我们需要下载受控 LinxISA LLVM 源码,切换到上文的精确 SHA,并以 +**动态库 (Shared Libs)** 模式编译,以确保 Python Binding 的正确链接。 ```bash # 1. 下载 LLVM 源码 cd $WORKSPACE_DIR -git clone https://github.com/llvm/llvm-project.git +git clone https://github.com/LinxISA/llvm-project.git cd $LLVM_SOURCE_DIR +git checkout --detach be1fc2451936a713e2a15620d5e7f8fe0a73688e -# 2. [关键] 切换到 llvmorg-19.1.7 -git checkout llvmorg-19.1.7 - -# 3. 配置 CMake (构建动态库并启用 Python 绑定) +# 2. 配置 CMake (构建动态库并启用 Python 绑定) cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ -DBUILD_SHARED_LIBS=ON \ @@ -103,14 +107,14 @@ cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DCMAKE_BUILD_TYPE=Release \ -DLLVM_TARGETS_TO_BUILD="host" -# 4. 编译 LLVM (这一步耗时较长) +# 3. 编译 LLVM (这一步耗时较长) ninja -C $LLVM_BUILD_DIR ``` ### 3.3 第二步:构建 PTOAS (Out-of-Tree) -下载 PTOAS 源码并基于刚刚编译好的 LLVM 19 进行构建。 +下载 PTOAS 源码并基于刚刚编译好的受控 LinxISA LLVM 进行构建。 ```bash # 1. 下载 PTOAS 源码 @@ -133,6 +137,7 @@ cmake -G Ninja \ -Dpybind11_DIR="${PYBIND11_CMAKE_DIR}" \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DMLIR_PYTHON_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core \ + -DPTOAS_ENABLE_WERROR=OFF \ -DCMAKE_INSTALL_PREFIX="$PTO_INSTALL_DIR" # 4. 编译并安装 diff --git a/lib/PTO/Transforms/PTOToEmitC.cpp b/lib/PTO/Transforms/PTOToEmitC.cpp index 812662e55b..2190c77155 100644 --- a/lib/PTO/Transforms/PTOToEmitC.cpp +++ b/lib/PTO/Transforms/PTOToEmitC.cpp @@ -290,14 +290,6 @@ static bool isEmitCTileLikeType(Type ty) { return value && (value->contains("Tile<") || value->contains("ConvTile<")); } -static Value loadEmitCLValueIfNeeded(OpBuilder &builder, Location loc, - Value value) { - if (auto lvalueTy = dyn_cast(value.getType())) - return builder.create(loc, lvalueTy.getValueType(), value) - .getResult(); - return value; -} - static std::string getEmitCScalarTypeToken(Type elemTy) { if (pto::isPTOFloat8E4M3FamilyType(elemTy)) return "float8_e4m3_t"; @@ -544,8 +536,9 @@ class PTOToEmitCTypeConverter : public TypeConverter { }); addConversion([Ctx](pto::EventIdArrayType type) -> Type { - std::string tok = "PTOAS_EventIdArray<" + std::to_string(type.getSize()) + ">"; - return emitc::OpaqueType::get(Ctx, tok); + return emitc::ArrayType::get( + ArrayRef{static_cast(type.getSize())}, + emitc::OpaqueType::get(Ctx, "int32_t")); }); // !pto.local_array -> !emitc.array. @@ -2668,9 +2661,6 @@ struct PTOMGatherToMGATHER : public OpConversionPattern { Value memArg = maybeWrapGlobalMemrefAsGlobalTensor( rewriter, op.getLoc(), mem, op.getMem().getType(), op.getOperation()); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); - memArg = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), memArg); - idx = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), idx); auto gatherOobTok = [&](pto::GatherOOB mode) -> StringRef { switch (mode) { @@ -2723,10 +2713,6 @@ struct PTOMGatherMaskToMGATHER_MASK Value dst = peelUnrealized(adaptor.getDst()); Value memArg = maybeWrapGlobalMemrefAsGlobalTensor( rewriter, op.getLoc(), mem, op.getMem().getType(), op.getOperation()); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); - memArg = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), memArg); - idx = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), idx); - mask = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), mask); rewriter.create( op.getLoc(), TypeRange{}, "MGATHER_MASK", @@ -2749,12 +2735,6 @@ struct PTOMGatherCasToMGATHER_CAS Value dst = peelUnrealized(adaptor.getDst()); Value memArg = maybeWrapGlobalMemrefAsGlobalTensor( rewriter, op.getLoc(), mem, op.getMem().getType(), op.getOperation()); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); - memArg = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), memArg); - idx = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), idx); - expected = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), expected); - replacement = - loadEmitCLValueIfNeeded(rewriter, op.getLoc(), replacement); rewriter.create( op.getLoc(), TypeRange{}, "MGATHER_CAS", @@ -4222,12 +4202,9 @@ struct PointerCastConversion : public OpConversionPattern { .getResult(0); } - Value assignDst = - loadEmitCLValueIfNeeded(rewriter, loc, resultValue); - rewriter.create( - loc, TypeRange{}, "TASSIGN", - ArrayAttr{}, ArrayAttr{}, - ValueRange{assignDst, addr}); + rewriter.create(loc, TypeRange{}, "TASSIGN", + ArrayAttr{}, ArrayAttr{}, + ValueRange{resultValue, addr}); rewriter.replaceOp(op, resultValue); return success(); @@ -4261,8 +4238,6 @@ struct PTOTLoadToTLOAD : public OpConversionPattern { srcArg = gt; } } - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); - srcArg = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), srcArg); rewriter.create( op.getLoc(), TypeRange{}, "TLOAD", @@ -4285,8 +4260,6 @@ struct PTOTPrefetchToTPREFETCH : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { Value address = peelUnrealized(adaptor.getAddress()); Value byteCount = peelUnrealized(adaptor.getByteCount()); - address = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), address); - byteCount = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), byteCount); rewriter.create( op.getLoc(), TypeRange{}, "TPREFETCH", @@ -4359,7 +4332,8 @@ struct PTOTStoreToTSTORE : public OpConversionPattern { const bool reluNonDefault = reluPreMode != pto::ReluPreMode::NoRelu; auto getOpaqueTok = [&](Value v, StringRef name) -> FailureOr { - if (auto ot = mlir::dyn_cast(v.getType())) + if (auto ot = + mlir::dyn_cast(getEmitCValueType(v.getType()))) return ot.getValue().str(); return rewriter.notifyMatchFailure(op, (name + " must be emitc::OpaqueType").str()); }; @@ -5392,9 +5366,6 @@ struct PTOMScatterToMSCATTER : public OpConversionPattern { Value memArg = maybeWrapGlobalMemrefAsGlobalTensor( rewriter, op.getLoc(), mem, op.getMem().getType(), op.getOperation()); - memArg = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), memArg); - src = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src); - idx = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), idx); auto scatterAtomicTok = [&](pto::ScatterAtomicOp atomic) -> StringRef { switch (atomic) { @@ -5460,10 +5431,6 @@ struct PTOMScatterMaskToMSCATTER_MASK Value mem = peelUnrealized(adaptor.getMem()); Value memArg = maybeWrapGlobalMemrefAsGlobalTensor( rewriter, op.getLoc(), mem, op.getMem().getType(), op.getOperation()); - memArg = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), memArg); - src = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src); - idx = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), idx); - mask = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), mask); rewriter.create( op.getLoc(), TypeRange{}, "MSCATTER_MASK", @@ -5575,8 +5542,6 @@ struct PTOGetScaleAddrToEmitC Value src = peelUnrealized(adaptor.getSrc()); Value dst = peelUnrealized(adaptor.getDst()); - src = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); rewriter.create( loc, TypeRange{}, "TGET_SCALE_ADDR", @@ -5601,11 +5566,7 @@ struct PTOSetValidShapeToEmitC : public OpConversionPattern bool { - auto ot = dyn_cast(v.getType()); - if (!ot) - return false; - StringRef s = ot.getValue(); - return s.contains("Tile<") || s.contains("ConvTile<"); + return isEmitCTileLikeType(v.getType()); }; Value src = peelAllCasts(peelUnrealized(adaptor.getSource())); @@ -5638,11 +5599,7 @@ struct PTOTAssignToEmitC : public OpConversionPattern { return v; }; auto isTileLike = [](Value v) -> bool { - auto ot = dyn_cast(v.getType()); - if (!ot) - return false; - StringRef s = ot.getValue(); - return s.contains("Tile<") || s.contains("ConvTile<"); + return isEmitCTileLikeType(v.getType()); }; auto loc = op.getLoc(); @@ -6500,8 +6457,10 @@ struct PTOEventIdArrayGetToEmitC return rewriter.notifyMatchFailure(op, "failed to map eventid_array get result type"); + auto slot = rewriter.create( + op.getLoc(), emitc::LValueType::get(resultTy), array, index); auto load = - rewriter.create(op.getLoc(), resultTy, array, index); + rewriter.create(op.getLoc(), resultTy, slot.getResult()); rewriter.replaceOp(op, load.getResult()); return success(); } @@ -6519,9 +6478,13 @@ struct PTOEventIdArraySetToEmitC Value index = peelUnrealized(adaptor.getIndex()); Value value = peelUnrealized(adaptor.getValue()); - rewriter.create( - op.getLoc(), TypeRange{}, "PTOAS__EVENTID_ARRAY_STORE", - ArrayAttr{}, ArrayAttr{}, ValueRange{array, index, value}); + Type elemTy = getEmitCValueType(value.getType()); + Value slot = rewriter + .create(op.getLoc(), + emitc::LValueType::get(elemTy), + array, ValueRange{index}) + .getResult(); + rewriter.create(op.getLoc(), slot, value); rewriter.eraseOp(op); return success(); } @@ -6572,9 +6535,12 @@ struct PTOLocalArrayGetToEmitC return rewriter.notifyMatchFailure( op, "failed to map local_array element type"); - auto sub = rewriter.create( - op.getLoc(), resultTy, adaptor.getArray(), adaptor.getIndices()); - rewriter.replaceOp(op, sub.getResult()); + auto slot = rewriter.create( + op.getLoc(), emitc::LValueType::get(resultTy), adaptor.getArray(), + adaptor.getIndices()); + auto load = + rewriter.create(op.getLoc(), resultTy, slot.getResult()); + rewriter.replaceOp(op, load.getResult()); return success(); } }; @@ -6591,12 +6557,12 @@ struct PTOLocalArraySetToEmitC OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Value value = adaptor.getValue(); - Type elemTy = value.getType(); + Type elemTy = getEmitCValueType(value.getType()); Value slot = rewriter - .create(op.getLoc(), elemTy, - adaptor.getArray(), - adaptor.getIndices()) + .create( + op.getLoc(), emitc::LValueType::get(elemTy), + adaptor.getArray(), adaptor.getIndices()) .getResult(); rewriter.create(op.getLoc(), slot, value); rewriter.eraseOp(op); @@ -6833,7 +6799,8 @@ struct PTOPartitionViewToEmitC }; static FailureOr getPipeDataTypeToken(Value value) { - auto opaqueTy = dyn_cast(value.getType()); + auto opaqueTy = + dyn_cast(getEmitCValueType(value.getType())); if (!opaqueTy) return failure(); StringRef token = opaqueTy.getValue(); @@ -7258,7 +7225,8 @@ struct PTOTCIToEmitC : public OpConversionPattern { std::string descTok = op.getDescending() ? "1" : "0"; ArrayAttr targs; - if (auto ot = mlir::dyn_cast(dst.getType())) { + if (auto ot = mlir::dyn_cast( + getEmitCValueType(dst.getType()))) { std::string tileTok = ot.getValue().str(); // "Tile<...>" targs = rewriter.getArrayAttr({ emitc::OpaqueAttr::get(ctx, tileTok), @@ -7480,7 +7448,8 @@ struct PTOTTriToEmitC : public OpConversionPattern { Value diagonal = peelUnrealized(adaptor.getDiagonal()); ArrayAttr templateArgs; - if (auto dstOT = mlir::dyn_cast(dst.getType())) { + if (auto dstOT = mlir::dyn_cast( + getEmitCValueType(dst.getType()))) { templateArgs = rewriter.getArrayAttr({ emitc::OpaqueAttr::get(ctx, dstOT.getValue().str()), emitc::OpaqueAttr::get(ctx, std::to_string(op.getUpperOrLower())), @@ -7783,7 +7752,6 @@ struct PTOAccCvtToEmitC : public OpConversionPattern { LogicalResult matchAndRewrite(pto::AccCvtOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Value dst = peelUnrealized(adaptor.getDst()); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); rewriter.create( op.getLoc(), TypeRange{}, "ACCCVT", /*args=*/ArrayAttr{}, /*templateArgs=*/ArrayAttr{}, @@ -8113,7 +8081,8 @@ struct PTOGatherToEmitC : public OpConversionPattern { Value src0 = peelUnrealized(adaptor.getSrc()); auto getOpaqueTok = [&](Value v, StringRef name) -> FailureOr { - if (auto ot = mlir::dyn_cast(v.getType())) + if (auto ot = + mlir::dyn_cast(getEmitCValueType(v.getType()))) return ot.getValue().str(); return rewriter.notifyMatchFailure(op, (name + " must be emitc::OpaqueType (tile)").str()); }; @@ -8232,8 +8201,6 @@ struct PTOImg2ColToEmitC : public OpConversionPattern { ConversionPatternRewriter &rewriter) const override { Value src = peelUnrealized(adaptor.getSrc()); Value dst = peelUnrealized(adaptor.getDst()); - src = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); rewriter.create( op.getLoc(), TypeRange{}, "TIMG2COL", @@ -8253,9 +8220,6 @@ struct PTODeinterleaveToEmitC : public OpConversionPattern Value src = peelUnrealized(adaptor.getSrc()); Value dst0 = peelUnrealized(adaptor.getDst0()); Value dst1 = peelUnrealized(adaptor.getDst1()); - src = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src); - dst0 = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst0); - dst1 = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst1); rewriter.create( op.getLoc(), TypeRange{}, "TDEINTERLEAVE", @@ -8275,9 +8239,6 @@ struct PTOInterleaveToEmitC : public OpConversionPattern { Value src0 = peelUnrealized(adaptor.getSrc0()); Value src1 = peelUnrealized(adaptor.getSrc1()); Value dst = peelUnrealized(adaptor.getDst()); - src0 = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src0); - src1 = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), src1); - dst = loadEmitCLValueIfNeeded(rewriter, op.getLoc(), dst); rewriter.create( op.getLoc(), TypeRange{}, "TINTERLEAVE", @@ -8445,8 +8406,10 @@ struct PTOMovToEmitC : public OpConversionPattern { if (op.getPreQuantScalar()) preQuantScalar = peelUnrealized(adaptor.getPreQuantScalar()); - auto dstOT = mlir::dyn_cast(dst.getType()); - auto srcOT = mlir::dyn_cast(src.getType()); + auto dstOT = + mlir::dyn_cast(getEmitCValueType(dst.getType())); + auto srcOT = + mlir::dyn_cast(getEmitCValueType(src.getType())); if (!dstOT || !srcOT) return rewriter.notifyMatchFailure( op, "tmov lowering expects opaque dst/src types"); @@ -8489,7 +8452,8 @@ struct PTOMovToEmitC : public OpConversionPattern { StringRef callee = "TMOV"; if (hasFp) { - auto fpOT = mlir::dyn_cast(fp.getType()); + auto fpOT = + mlir::dyn_cast(getEmitCValueType(fp.getType())); if (!fpOT) return rewriter.notifyMatchFailure( op, "tmov fp lowering expects opaque fp type"); @@ -8554,9 +8518,12 @@ struct PTOMovFPToEmitC : public OpConversionPattern { // TMOV_FP(dstTileData, cTile, fbTile) ArrayAttr templateArgs; - auto dstOT = mlir::dyn_cast(dst.getType()); - auto srcOT = mlir::dyn_cast(src.getType()); - auto fpOT = mlir::dyn_cast(fp.getType()); + auto dstOT = + mlir::dyn_cast(getEmitCValueType(dst.getType())); + auto srcOT = + mlir::dyn_cast(getEmitCValueType(src.getType())); + auto fpOT = + mlir::dyn_cast(getEmitCValueType(fp.getType())); if (dstOT && srcOT && fpOT) { templateArgs = rewriter.getArrayAttr({ emitc::OpaqueAttr::get(ctx, dstOT.getValue().str()), @@ -8594,7 +8561,8 @@ struct PTOQuantToEmitC : public OpConversionPattern { Value offsetPtr; if (op.getOffset()) { Value offset = peelUnrealized(adaptor.getOffset()); - auto offsetOT = mlir::dyn_cast(offset.getType()); + auto offsetOT = mlir::dyn_cast( + getEmitCValueType(offset.getType())); if (offsetOT) { offsetPtr = rewriter .create( @@ -8609,9 +8577,12 @@ struct PTOQuantToEmitC : public OpConversionPattern { ? "pto::QuantType::INT8_SYM" : "pto::QuantType::INT8_ASYM"; ArrayAttr templateArgs; - auto dstOT = mlir::dyn_cast(dst.getType()); - auto srcOT = mlir::dyn_cast(src.getType()); - auto fpOT = mlir::dyn_cast(fp.getType()); + auto dstOT = + mlir::dyn_cast(getEmitCValueType(dst.getType())); + auto srcOT = + mlir::dyn_cast(getEmitCValueType(src.getType())); + auto fpOT = + mlir::dyn_cast(getEmitCValueType(fp.getType())); if (dstOT && srcOT && fpOT) { templateArgs = rewriter.getArrayAttr({ emitc::OpaqueAttr::get(ctx, quantTypeStr), @@ -8651,9 +8622,12 @@ struct PTODequantToEmitC : public OpConversionPattern { // TDEQUANT(dst, src, scale, offset) ArrayAttr templateArgs; - auto dstOT = mlir::dyn_cast(dst.getType()); - auto srcOT = mlir::dyn_cast(src.getType()); - auto scaleOT = mlir::dyn_cast(scale.getType()); + auto dstOT = + mlir::dyn_cast(getEmitCValueType(dst.getType())); + auto srcOT = + mlir::dyn_cast(getEmitCValueType(src.getType())); + auto scaleOT = + mlir::dyn_cast(getEmitCValueType(scale.getType())); if (dstOT && srcOT && scaleOT) { templateArgs = rewriter.getArrayAttr({ emitc::OpaqueAttr::get(ctx, dstOT.getValue().str()), @@ -8709,8 +8683,10 @@ struct PTOMrgSortToEmitC : public OpConversionPattern { for (Value v : adaptor.getSrcs()) srcs.push_back(peelUnrealized(v)); - auto dstOT = mlir::dyn_cast(dst.getType()); - auto tmpOT = mlir::dyn_cast(tmp.getType()); + auto dstOT = + mlir::dyn_cast(getEmitCValueType(dst.getType())); + auto tmpOT = + mlir::dyn_cast(getEmitCValueType(tmp.getType())); if (!dstOT || !tmpOT || srcs.size() < 2 || srcs.size() > 4) return op.emitOpError("format2 expects dst/tmp tilebufs and 2 to 4 srcs"); @@ -8719,7 +8695,8 @@ struct PTOMrgSortToEmitC : public OpConversionPattern { targs.push_back(emitc::OpaqueAttr::get(ctx, dstOT.getValue().str())); targs.push_back(emitc::OpaqueAttr::get(ctx, tmpOT.getValue().str())); for (Value v : srcs) { - auto ot = mlir::dyn_cast(v.getType()); + auto ot = + mlir::dyn_cast(getEmitCValueType(v.getType())); if (!ot) return op.emitOpError("format2 expects tilebuf srcs"); targs.push_back(emitc::OpaqueAttr::get(ctx, ot.getValue().str())); @@ -9919,9 +9896,6 @@ struct PTOSORTToEmitC : public OpConversionPattern { Value src = peelUnrealized(adaptor.getSrc()); Value dst = peelUnrealized(adaptor.getDst()); Value dstIndices = peelUnrealized(adaptor.getDstIndices()); - src = loadEmitCLValueIfNeeded(rewriter, loc, src); - dst = loadEmitCLValueIfNeeded(rewriter, loc, dst); - dstIndices = loadEmitCLValueIfNeeded(rewriter, loc, dstIndices); auto boolTy = emitc::OpaqueType::get(ctx, "bool"); Value descending = rewriter.create( loc, boolTy, @@ -10806,10 +10780,9 @@ struct PTOAllocTileToEmitC addr = rewriter.create(loc, u64Ty, addr).getResult(); } - Value tileValue = loadEmitCLValueIfNeeded(rewriter, loc, tile); rewriter.create(loc, TypeRange{}, "TASSIGN", ArrayAttr{}, ArrayAttr{}, - ValueRange{tileValue, addr}); + ValueRange{tile, addr}); } rewriter.replaceOp(op, tile); @@ -12356,6 +12329,15 @@ static AICORE inline void ptoas_auto_sync_tail( return; } + if (auto lvalueTy = dyn_cast(inTy); + lvalueTy && lvalueTy.getValueType() == outTy) { + OpBuilder builder(cast); + auto load = builder.create(cast.getLoc(), outTy, input); + output.replaceAllUsesWith(load.getResult()); + castsToErase.push_back(cast); + return; + } + // SCF/CFG type conversion can transiently materialize pointer->memref // bridge casts. At this stage, the producing value is already in the // lowered EmitC pointer form; keep it and drop the bridge cast. @@ -12429,6 +12411,125 @@ static AICORE inline void ptoas_auto_sync_tail( } } + // EmitC models mutable variables as lvalues, while call_opaque accepts + // value operands. Void calls are instruction statements and may mutate + // tile/output arguments, so preserve their lvalue identity with verbatim + // placeholders. Calls producing a value are input expressions; materialize + // reads for those operands before call_opaque verification. + { + SmallVector calls; + mop.walk([&](emitc::CallOpaqueOp call) { calls.push_back(call); }); + + for (emitc::CallOpaqueOp call : calls) { + bool hasLValueOperand = llvm::any_of(call->getOperandTypes(), + llvm::IsaPred); + if (!hasLValueOperand) + continue; + + if (call->getNumResults() == 0) { + auto escapeFormat = [](StringRef text) { + std::string escaped; + escaped.reserve(text.size()); + for (char ch : text) { + escaped.push_back(ch); + if (ch == '{' || ch == '}') + escaped.push_back(ch); + } + return escaped; + }; + auto renderAttribute = + [&](Attribute attr) -> std::optional { + if (auto opaque = dyn_cast(attr)) + return opaque.getValue().str(); + if (auto integer = dyn_cast(attr)) { + SmallString<32> value; + integer.getValue().toString( + value, 10, + /*Signed=*/!integer.getType().isUnsignedInteger()); + return value.str().str(); + } + return std::nullopt; + }; + + std::string format = escapeFormat(call.getCallee()); + bool canUseVerbatim = true; + if (auto templateArgs = call.getTemplateArgs()) { + format += "<"; + for (auto [index, attr] : llvm::enumerate(*templateArgs)) { + if (index) + format += ", "; + auto rendered = renderAttribute(attr); + if (!rendered) { + canUseVerbatim = false; + break; + } + format += escapeFormat(*rendered); + } + format += ">"; + } + + SmallVector formatOperands; + format += "("; + if (canUseVerbatim) { + if (auto args = call.getArgs()) { + for (auto [index, attr] : llvm::enumerate(*args)) { + if (index) + format += ", "; + if (auto integer = dyn_cast(attr); + integer && integer.getType().isIndex()) { + int64_t operandIndex = integer.getInt(); + if (operandIndex < 0 || + operandIndex >= + static_cast(call.getNumOperands())) { + canUseVerbatim = false; + break; + } + format += "{}"; + formatOperands.push_back(call->getOperand(operandIndex)); + continue; + } + auto rendered = renderAttribute(attr); + if (!rendered) { + canUseVerbatim = false; + break; + } + format += escapeFormat(*rendered); + } + } else { + for (auto [index, operand] : + llvm::enumerate(call.getOperands())) { + if (index) + format += ", "; + format += "{}"; + formatOperands.push_back(operand); + } + } + } + format += ");"; + + if (canUseVerbatim) { + OpBuilder builder(call); + builder.setInsertionPoint(call); + builder.create( + call.getLoc(), builder.getStringAttr(format), formatOperands); + call.erase(); + continue; + } + } + + OpBuilder builder(call); + builder.setInsertionPoint(call); + for (OpOperand &operand : call->getOpOperands()) { + auto lvalueTy = dyn_cast(operand.get().getType()); + if (!lvalueTy) + continue; + auto load = builder.create( + call.getLoc(), lvalueTy.getValueType(), operand.get()); + operand.set(load.getResult()); + } + } + } + // --- Step B: 修复 Loop 归纳变量 (IV) --- // 此时 emitc.for 的 operand 已经是 int32 了,我们检查 IV 是否匹配,不匹配则修正 mop.walk([&](emitc::ForOp forOp) { diff --git a/test/lit/pto/async_put_get_emitc.pto b/test/lit/pto/async_put_get_emitc.pto index b191dbd0bb..bd45df82e0 100644 --- a/test/lit/pto/async_put_get_emitc.pto +++ b/test/lit/pto/async_put_get_emitc.pto @@ -15,7 +15,7 @@ module { } // A3-LABEL: AICORE void async_put_get( -// A3: Tile [[SCRATCH:v[0-9]+]]; +// A3: Tile [[SCRATCH:v[0-9]+]]; // A3: TASSIGN([[SCRATCH]], [[SCRATCH_ADDR:v[0-9]+]]); // A3: pto::comm::AsyncSession [[SESSION:v[0-9]+]]; // A3: pto::comm::sdma::SdmaBaseConfig [[CFG:v[0-9]+]] = {32768ULL, 0ULL, 1u}; @@ -32,5 +32,5 @@ module { // A3: [[GLTNSRTY]] [[GT1:v[0-9]+]] = [[GLTNSRTY]]({{.*}}, [[SHAPE1]], [[STRIDE1]]); // A3: pto::comm::AsyncEvent [[PUT_EVT:v[0-9]+]] = pto::comm::TPUT_ASYNC( // A3: pto::comm::AsyncEvent [[GET_EVT:v[0-9]+]] = pto::comm::TGET_ASYNC( -// A3: bool [[PUT_DONE:v[0-9]+]] = [[PUT_EVT]].Wait([[SESSION]]); -// A3: bool [[GET_DONE:v[0-9]+]] = [[GET_EVT]].Test([[SESSION]]); +// A3: bool [[PUT_DONE:v[0-9]+]] = [[PUT_EVT]].Wait({{v[0-9]+}}); +// A3: bool [[GET_DONE:v[0-9]+]] = [[GET_EVT]].Test({{v[0-9]+}}); diff --git a/test/lit/pto/backedge_nested_same_pipe_prune_regression.pto b/test/lit/pto/backedge_nested_same_pipe_prune_regression.pto index 426f60ecb7..a6bc9edc76 100644 --- a/test/lit/pto/backedge_nested_same_pipe_prune_regression.pto +++ b/test/lit/pto/backedge_nested_same_pipe_prune_regression.pto @@ -5,13 +5,13 @@ // the wider same-pipe pair can be removed before event-id allocation. // // CHECK-LABEL: __global__ AICORE void backedge_nested_same_pipe_prune() -// CHECK: for (size_t v16 = (size_t) v1; v16 < ((size_t) v3); v16 += (size_t) v2) { +// CHECK: for (int32_t // CHECK-NEXT: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[INNER:[0-9]+]]); -// CHECK-NEXT: TMOV(v10, v6); -// CHECK-NEXT: TMOV(v12, v8); -// CHECK: TMATMUL(v14, v10, v12); -// CHECK: TMOV(v8, v14); -// CHECK-NEXT: TMOV(v6, v14); +// CHECK-NEXT: TMOV(v8, v6); +// CHECK-NEXT: TMOV(v9, v7); +// CHECK: TMATMUL(v10, v8, v9); +// CHECK: TMOV(v7, v10); +// CHECK-NEXT: TMOV(v6, v10); // CHECK-NEXT: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[INNER]]); // CHECK-NEXT: } // CHECK-NEXT: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[INNER]]); diff --git a/test/lit/pto/comm_collective_emitc.pto b/test/lit/pto/comm_collective_emitc.pto index 01c3afcebf..60c93428a0 100644 --- a/test/lit/pto/comm_collective_emitc.pto +++ b/test/lit/pto/comm_collective_emitc.pto @@ -30,10 +30,10 @@ module { } } -// A3: pto::comm::ReduceOp::Max -// A3: pto::comm::ReduceOp::Sum -// A3: pto::comm::ParallelGroup -// A3: pto::comm::TBROADCAST( -// A3: pto::comm::TGATHER( -// A3: pto::comm::TSCATTER( -// A3: pto::comm::TREDUCE( +// A3-DAG: pto::comm::ReduceOp::Max +// A3-DAG: pto::comm::ReduceOp::Sum +// A3-DAG: pto::comm::ParallelGroup +// A3-DAG: pto::comm::TBROADCAST( +// A3-DAG: pto::comm::TGATHER( +// A3-DAG: pto::comm::TSCATTER( +// A3-DAG: pto::comm::TREDUCE( diff --git a/test/lit/pto/comm_p2p_emitc.pto b/test/lit/pto/comm_p2p_emitc.pto index 191b767770..cbf934f096 100644 --- a/test/lit/pto/comm_p2p_emitc.pto +++ b/test/lit/pto/comm_p2p_emitc.pto @@ -25,13 +25,13 @@ module { } } -// A3: pto::comm::WaitCmp::EQ -// A3: pto::comm::WaitCmp::GE -// A3: pto::comm::NotifyOp::Set -// A3: pto::comm::TPUT( -// A3: pto::comm::TPUT( -// A3: pto::comm::TGET( -// A3: pto::comm::TGET( -// A3: pto::comm::TNOTIFY( -// A3: pto::comm::TWAIT( -// A3: pto::comm::TTEST( +// A3-DAG: pto::comm::WaitCmp::EQ +// A3-DAG: pto::comm::WaitCmp::GE +// A3-DAG: pto::comm::NotifyOp::Set +// A3-DAG: pto::comm::TPUT( +// A3-DAG: pto::comm::TPUT( +// A3-DAG: pto::comm::TGET( +// A3-DAG: pto::comm::TGET( +// A3-DAG: pto::comm::TNOTIFY( +// A3-DAG: pto::comm::TWAIT( +// A3-DAG: pto::comm::TTEST( diff --git a/test/lit/pto/eventid_array_dyn_sync.pto b/test/lit/pto/eventid_array_dyn_sync.pto index f8fed9630c..8855f7021a 100644 --- a/test/lit/pto/eventid_array_dyn_sync.pto +++ b/test/lit/pto/eventid_array_dyn_sync.pto @@ -15,9 +15,10 @@ module { // CHECK-LABEL: __global__ AICORE void eventid_array_dyn_sync() { // CHECK: const int32_t {{v[0-9]+}} = 0; -// CHECK: PTOAS_EventIdArray<4> {{v[0-9]+}}; -// CHECK: {{v[0-9]+}}[{{v[0-9]+}}] = {{v[0-9]+}}; -// CHECK: event_t {{v[0-9]+}} = (event_t) {{v[0-9]+}}[{{v[0-9]+}}]; -// CHECK: set_flag(PIPE_MTE2, PIPE_MTE3, {{v[0-9]+}}); -// CHECK: event_t {{v[0-9]+}} = (event_t) {{v[0-9]+}}[{{v[0-9]+}}]; -// CHECK: wait_flag(PIPE_MTE2, PIPE_MTE3, {{v[0-9]+}}); +// CHECK: int32_t [[ARR:v[0-9]+]][4]; +// CHECK: [[ARR]][{{v[0-9]+}}] = {{v[0-9]+}}; +// CHECK: int32_t [[EID:v[0-9]+]] = [[ARR]][{{v[0-9]+}}]; +// CHECK: event_t [[SET_EID:v[0-9]+]] = (event_t) [[EID]]; +// CHECK: set_flag(PIPE_MTE2, PIPE_MTE3, [[SET_EID]]); +// CHECK: event_t [[WAIT_EID:v[0-9]+]] = (event_t) [[EID]]; +// CHECK: wait_flag(PIPE_MTE2, PIPE_MTE3, [[WAIT_EID]]); diff --git a/test/lit/pto/eventid_array_get_set_get.pto b/test/lit/pto/eventid_array_get_set_get.pto index 5aa8269933..bfca7bb00c 100644 --- a/test/lit/pto/eventid_array_get_set_get.pto +++ b/test/lit/pto/eventid_array_get_set_get.pto @@ -17,10 +17,12 @@ module { } // CHECK-LABEL: __global__ AICORE void eventid_array_get_set_get() { -// CHECK: PTOAS_EventIdArray<4> [[ARR:v[0-9]+]]; +// CHECK: int32_t [[ARR:v[0-9]+]][4]; // CHECK: [[ARR]][{{v[0-9]+}}] = {{v[0-9]+}}; +// CHECK: int32_t [[FIRST_VALUE:v[0-9]+]] = [[ARR]][{{v[0-9]+}}]; // CHECK: [[ARR]][{{v[0-9]+}}] = {{v[0-9]+}}; -// CHECK: event_t [[FIRST:v[0-9]+]] = (event_t) [[ARR]][{{v[0-9]+}}]; +// CHECK: int32_t [[SECOND_VALUE:v[0-9]+]] = [[ARR]][{{v[0-9]+}}]; +// CHECK: event_t [[FIRST:v[0-9]+]] = (event_t) [[FIRST_VALUE]]; // CHECK: set_flag(PIPE_MTE2, PIPE_MTE3, [[FIRST]]); -// CHECK: event_t [[SECOND:v[0-9]+]] = (event_t) [[ARR]][{{v[0-9]+}}]; +// CHECK: event_t [[SECOND:v[0-9]+]] = (event_t) [[SECOND_VALUE]]; // CHECK: wait_flag(PIPE_MTE2, PIPE_MTE3, [[SECOND]]); diff --git a/test/lit/pto/eventid_array_no_cse.pto b/test/lit/pto/eventid_array_no_cse.pto index eecea2ab19..a8724bc9be 100644 --- a/test/lit/pto/eventid_array_no_cse.pto +++ b/test/lit/pto/eventid_array_no_cse.pto @@ -18,7 +18,7 @@ module { } // CHECK-LABEL: __global__ AICORE void eventid_array_no_cse() { -// CHECK: PTOAS_EventIdArray<4> [[ARR0:v[0-9]+]]; -// CHECK: PTOAS_EventIdArray<4> [[ARR1:v[0-9]+]]; +// CHECK: int32_t [[ARR0:v[0-9]+]][4]; +// CHECK: int32_t [[ARR1:v[0-9]+]][4]; // CHECK: [[ARR0]][{{v[0-9]+}}] = {{v[0-9]+}}; // CHECK: [[ARR1]][{{v[0-9]+}}] = {{v[0-9]+}}; diff --git a/test/lit/pto/issue428_cube_sync_regression.pto b/test/lit/pto/issue428_cube_sync_regression.pto index 9f129c6705..004d06b413 100644 --- a/test/lit/pto/issue428_cube_sync_regression.pto +++ b/test/lit/pto/issue428_cube_sync_regression.pto @@ -5,98 +5,98 @@ // - Preserve the tail drain before the auto tail barrier helper. // // CHECK-LABEL: tri_inv_block2x2_fp16( -// CHECK: for (size_t v71 = v20; v71 < v22; v71 += v19) { +// CHECK: for (int32_t {{[a-z][0-9]+}} = {{v[0-9]+}}; {{[a-z][0-9]+}} < {{v[0-9]+}}; {{[a-z][0-9]+}} += {{v[0-9]+}}) { // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID2); -// CHECK: TMOV(v65, v49); -// CHECK: TMOV(v67, v61); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID2); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID2); // CHECK: wait_flag(PIPE_FIX, PIPE_M, EVENT_ID2); -// CHECK: TMATMUL(v69, v65, v67); +// CHECK: TMATMUL({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID3); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID3); -// CHECK: TMOV(v67, v51); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID3); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID3); -// CHECK: TMATMUL_ACC(v69, v69, v65, v67); +// CHECK: TMATMUL_ACC({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID4); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID2); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID2); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID4); -// CHECK: if ((int32_t) ((uint32_t) ((int32_t) v71) + (uint32_t) v8) < v21) { -// CHECK: TMOV(v49, v69); +// CHECK: if ( +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_FIX, PIPE_M, EVENT_ID3); -// CHECK: TMOV(v65, v51); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID4); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID4); // CHECK: wait_flag(PIPE_FIX, PIPE_M, EVENT_ID3); -// CHECK: TMATMUL(v69, v65, v67); +// CHECK: TMATMUL({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID3); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID3); -// CHECK: TMOV(v51, v69); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID2); // CHECK: set_flag(PIPE_FIX, PIPE_M, EVENT_ID2); // CHECK: } -// CHECK: for (size_t v72 = v20; v72 < v22; v72 += v19) { +// CHECK: for (int32_t {{[a-z][0-9]+}} = {{v[0-9]+}}; {{[a-z][0-9]+}} < {{v[0-9]+}}; {{[a-z][0-9]+}} += {{v[0-9]+}}) { // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID7); -// CHECK: TMOV(v65, v53); -// CHECK: TMOV(v67, v61); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID6); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID6); // CHECK: wait_flag(PIPE_FIX, PIPE_M, EVENT_ID6); -// CHECK: TMATMUL(v69, v65, v67); +// CHECK: TMATMUL({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID0); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID0); -// CHECK: TMOV(v67, v55); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID7); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID7); -// CHECK: TMATMUL_ACC(v69, v69, v65, v67); +// CHECK: TMATMUL_ACC({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID0); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID6); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID6); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID0); -// CHECK: if ((int32_t) ((uint32_t) ((int32_t) v72) + (uint32_t) v8) < v21) { -// CHECK: TMOV(v53, v69); +// CHECK: if ( +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_FIX, PIPE_M, EVENT_ID7); -// CHECK: TMOV(v65, v55); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); // CHECK: wait_flag(PIPE_FIX, PIPE_M, EVENT_ID7); -// CHECK: TMATMUL(v69, v65, v67); +// CHECK: TMATMUL({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID7); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID7); -// CHECK: TMOV(v55, v69); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID7); // CHECK: set_flag(PIPE_FIX, PIPE_M, EVENT_ID6); // CHECK: } // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID0); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID0); -// CHECK: TMOV(v53, v69); -// CHECK: TSTORE(v48, v69); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); +// CHECK: TSTORE({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_FIX, PIPE_M, EVENT_ID0); -// CHECK: TLOAD(v57, v36); +// CHECK: TLOAD({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE2, PIPE_MTE1, EVENT_ID3); // CHECK: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID0); -// CHECK: TMOV(v65, v53); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: wait_flag(PIPE_MTE2, PIPE_MTE1, EVENT_ID3); -// CHECK: TMOV(v67, v57); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); // CHECK: wait_flag(PIPE_FIX, PIPE_M, EVENT_ID0); -// CHECK: TMATMUL(v69, v65, v67); +// CHECK: TMATMUL({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID0); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID0); -// CHECK: TMOV(v63, v69); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID1); // CHECK: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID1); -// CHECK: TMOV(v65, v63); -// CHECK: TMOV(v67, v49); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); +// CHECK: TMOV({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); -// CHECK: TMATMUL(v69, v65, v67); +// CHECK: TMATMUL({{v[0-9]+}}, {{v[0-9]+}}, {{v[0-9]+}}); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID0); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID0); -// CHECK: TSTORE(v45, v69); +// CHECK: TSTORE({{v[0-9]+}}, {{v[0-9]+}}); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID2); // CHECK: wait_flag(PIPE_FIX, PIPE_M, EVENT_ID2); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID7); diff --git a/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression.pto b/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression.pto index 35e66d9253..e706bcabb6 100644 --- a/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression.pto +++ b/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression.pto @@ -6,27 +6,27 @@ // // CHECK-LABEL: __global__ AICORE void loop_if_else_loop_carried_sync() // CHECK: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID1); -// CHECK: TMOV(v10, v6); -// CHECK: TMOV(v12, v8); +// CHECK: TMOV(v8, v6); +// CHECK: TMOV(v9, v7); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID0); -// CHECK: TMATMUL(v14, v10, v12); +// CHECK: TMATMUL(v10, v8, v9); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID0); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID0); -// CHECK: TMOV(v6, v14); +// CHECK: TMOV(v6, v10); // CHECK: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID0); // CHECK: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID0); -// CHECK: for (size_t v16 = (size_t) v1; v16 < ((size_t) v3); v16 += (size_t) v2) { +// CHECK: for (int32_t // CHECK: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID1); -// CHECK: TMOV(v10, v6); +// CHECK: TMOV(v8, v6); // CHECK: set_flag(PIPE_MTE1, PIPE_M, EVENT_ID1); // CHECK: wait_flag(PIPE_MTE1, PIPE_M, EVENT_ID1); -// CHECK: TMATMUL(v14, v10, v12); +// CHECK: TMATMUL(v10, v8, v9); // CHECK: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID0); // CHECK: set_flag(PIPE_M, PIPE_FIX, EVENT_ID1); // CHECK: wait_flag(PIPE_M, PIPE_FIX, EVENT_ID1); // CHECK: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID0); -// CHECK: if ((int32_t) v16 == v1) { +// CHECK: if ( // CHECK: } else { // CHECK: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID1); // CHECK: ptoas_auto_sync_tail(PTOAutoSyncTailMode::kBarrierAll); diff --git a/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression_gss.pto b/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression_gss.pto index 5b1dc779c3..20ac5f3245 100644 --- a/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression_gss.pto +++ b/test/lit/pto/issue454_loop_if_else_loop_carried_sync_regression_gss.pto @@ -5,8 +5,8 @@ // // CHECK-DAG: pipe_barrier(PIPE_ALL) // CHECK-LABEL: __global__ AICORE void loop_if_else_loop_carried_sync() -// CHECK: for (size_t -// CHECK: if ((int32_t) +// CHECK: for (int32_t +// CHECK: if ( // CHECK: } else { // CHECK: #endif // __DAV_CUBE__ diff --git a/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression.pto b/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression.pto index 0e61be5274..3a5f62a8e1 100644 --- a/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression.pto +++ b/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression.pto @@ -5,9 +5,9 @@ // - Inner-loop and outer-loop event chains must both keep their set/wait handshake. // // CHECK-LABEL: __global__ AICORE void nested_loop_same_pipe_pair() -// CHECK: for (size_t +// CHECK: for (int32_t // CHECK: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[OUT:[0-9]+]]); -// CHECK: for (size_t +// CHECK: for (int32_t // CHECK: wait_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[IN:[0-9]+]]); // CHECK: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[IN]]); // CHECK: set_flag(PIPE_FIX, PIPE_MTE1, EVENT_ID[[OUT]]); diff --git a/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression_gss.pto b/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression_gss.pto index ebd6179334..4b1a209674 100644 --- a/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression_gss.pto +++ b/test/lit/pto/issue454_nested_loop_same_pipe_pair_regression_gss.pto @@ -8,7 +8,7 @@ // - Inner-loop and outer-loop event chains must both keep their set/wait handshake. // // CHECK-LABEL: __global__ AICORE void nested_loop_same_pipe_pair() -// CHECK-COUNT-2: for (size_t +// CHECK-COUNT-2: for (int32_t // CHECK: TMATMUL( // CHECK: #endif // __DAV_CUBE__ diff --git a/test/lit/pto/issue533_loop_zero_trip_sync_regression.pto b/test/lit/pto/issue533_loop_zero_trip_sync_regression.pto index 7c404a254f..30033d5640 100644 --- a/test/lit/pto/issue533_loop_zero_trip_sync_regression.pto +++ b/test/lit/pto/issue533_loop_zero_trip_sync_regression.pto @@ -4,7 +4,7 @@ // a post-loop vector consumer must wait on MTE2->V before TROWEXPANDDIV. // // CHECK-LABEL: __global__ AICORE void qwen3_scope2_incore_5 -// CHECK: for (size_t +// CHECK: for (int32_t // CHECK: } // CHECK: wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID[[POST:[0-9]+]]); // CHECK: TROWEXPANDDIV diff --git a/test/lit/pto/issue533_loop_zero_trip_sync_regression_gss.pto b/test/lit/pto/issue533_loop_zero_trip_sync_regression_gss.pto index 5467978208..5536d1d3ae 100644 --- a/test/lit/pto/issue533_loop_zero_trip_sync_regression_gss.pto +++ b/test/lit/pto/issue533_loop_zero_trip_sync_regression_gss.pto @@ -4,7 +4,7 @@ // a post-loop vector consumer must wait on MTE2->V before TROWEXPANDDIV. // // CHECK-LABEL: __global__ AICORE void qwen3_scope2_incore_5 -// CHECK: for (size_t +// CHECK: for (int32_t // CHECK: } // CHECK: wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID[[POST:[0-9]+]]); // CHECK: TROWEXPANDDIV diff --git a/test/lit/pto/issue564_k_loop_mte1_mte2_wait_regression.pto b/test/lit/pto/issue564_k_loop_mte1_mte2_wait_regression.pto index 9c5b00dd1f..cf2dbe016d 100644 --- a/test/lit/pto/issue564_k_loop_mte1_mte2_wait_regression.pto +++ b/test/lit/pto/issue564_k_loop_mte1_mte2_wait_regression.pto @@ -10,7 +10,7 @@ // CHECK-NEXT: set_flag(PIPE_M, PIPE_MTE1, EVENT_ID[[CARRY:[0-9]+]]); // CHECK-NEXT: wait_flag(PIPE_MTE1, PIPE_MTE2, EVENT_ID[[PRE0:[0-9]+]]); // CHECK-NEXT: wait_flag(PIPE_M, PIPE_MTE1, EVENT_ID[[CARRY]]); -// CHECK-NEXT: for (size_t +// CHECK-NEXT: for (int32_t // CHECK: wait_flag(PIPE_MTE1, PIPE_MTE2, EVENT_ID[[LOAD0:[0-9]+]]); // CHECK-NEXT: TLOAD( // CHECK: wait_flag(PIPE_MTE1, PIPE_MTE2, EVENT_ID[[LOAD1:[0-9]+]]); diff --git a/test/lit/pto/issue622_v_mte2_eventid_overlap_reproducer.pto b/test/lit/pto/issue622_v_mte2_eventid_overlap_reproducer.pto index bb1e828b50..81d7ac4414 100644 --- a/test/lit/pto/issue622_v_mte2_eventid_overlap_reproducer.pto +++ b/test/lit/pto/issue622_v_mte2_eventid_overlap_reproducer.pto @@ -25,8 +25,8 @@ // CHECK-NEXT: TFREE, // CHECK-NEXT: wait_flag(PIPE_MTE3, PIPE_V, EVENT_ID[[OUTER_CARRY]]); // CHECK-NEXT: wait_flag(PIPE_V, PIPE_MTE2, EVENT_ID[[OUTER_TAIL]]); -// CHECK-NEXT: for (size_t -// CHECK: }; +// CHECK-NEXT: for (int32_t +// CHECK: } // CHECK-NEXT: set_flag(PIPE_MTE3, PIPE_V, EVENT_ID[[TAIL_HEAD:[0-9]+]]); // CHECK-NEXT: set_flag(PIPE_V, PIPE_MTE2, EVENT_ID[[POST_LOOP:[0-9]+]]); // CHECK-NEXT: wait_flag(PIPE_V, PIPE_MTE2, EVENT_ID[[POST_LOOP]]); diff --git a/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto b/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto index ff2c254f4b..c50786b08c 100644 --- a/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto +++ b/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto @@ -61,15 +61,14 @@ module { } // CHECK: AICORE void vector_while_kernel() -// CHECK: const int64_t {{v[0-9]+}} = 0; -// CHECK: const float {{v[0-9]+}} = 10.0f; -// CHECK: const float {{v[0-9]+}} = 1.0f; -// CHECK: const bool {{v[0-9]+}} = false; -// CHECK: const bool {{v[0-9]+}} = true; -// CHECK: const int32_t {{v[0-9]+}} = 4; -// CHECK: const int32_t {{v[0-9]+}} = 2; -// CHECK: const int32_t {{v[0-9]+}} = 1; -// CHECK: const int32_t {{v[0-9]+}} = 0; +// CHECK-DAG: const int64_t {{v[0-9]+}} = 0; +// CHECK-DAG: const float {{v[0-9]+}} = 10.0f; +// CHECK-DAG: const float {{v[0-9]+}} = 1.0f; +// CHECK-DAG: const bool {{v[0-9]+}} = true; +// CHECK-DAG: const int32_t {{v[0-9]+}} = 4; +// CHECK-DAG: const int32_t {{v[0-9]+}} = 2; +// CHECK-DAG: const int32_t {{v[0-9]+}} = 1; +// CHECK-DAG: const int32_t {{v[0-9]+}} = 0; // CHECK: using T = float; // CHECK: #if defined(__DAV_VEC__) // CHECK: set_mask_norm(); diff --git a/test/lit/pto/local_array_1d_emitc.pto b/test/lit/pto/local_array_1d_emitc.pto index a423f6f0ca..b4cb0118e2 100644 --- a/test/lit/pto/local_array_1d_emitc.pto +++ b/test/lit/pto/local_array_1d_emitc.pto @@ -25,4 +25,5 @@ module { // CHECK-LABEL: local_array_1d // CHECK: int32_t [[A:v[0-9]+]][16]; // CHECK: [[A]][{{v[0-9]+}}] = {{v[0-9]+}}; -// CHECK: [[A]][{{v[0-9]+}}] = [[A]][{{v[0-9]+}}]; +// CHECK: int32_t [[R:v[0-9]+]] = [[A]][{{v[0-9]+}}]; +// CHECK: [[A]][{{v[0-9]+}}] = [[R]]; diff --git a/test/lit/pto/local_array_2d_emitc.pto b/test/lit/pto/local_array_2d_emitc.pto index 7d419176a9..27e4695fda 100644 --- a/test/lit/pto/local_array_2d_emitc.pto +++ b/test/lit/pto/local_array_2d_emitc.pto @@ -25,4 +25,5 @@ module { // CHECK-LABEL: local_array_2d // CHECK: float [[M:v[0-9]+]][8][8]; // CHECK: [[M]][{{v[0-9]+}}][{{v[0-9]+}}] = {{v[0-9]+}}; -// CHECK: [[M]][{{v[0-9]+}}][{{v[0-9]+}}] = [[M]][{{v[0-9]+}}][{{v[0-9]+}}]; +// CHECK: float [[R:v[0-9]+]] = [[M]][{{v[0-9]+}}][{{v[0-9]+}}]; +// CHECK: [[M]][{{v[0-9]+}}][{{v[0-9]+}}] = [[R]]; diff --git a/test/lit/pto/local_array_get_rvalue_emitc.pto b/test/lit/pto/local_array_get_rvalue_emitc.pto index e0d020c66b..d276042837 100644 --- a/test/lit/pto/local_array_get_rvalue_emitc.pto +++ b/test/lit/pto/local_array_get_rvalue_emitc.pto @@ -25,7 +25,9 @@ module { // CHECK-LABEL: local_array_get_rvalue // CHECK: int32_t [[A:v[0-9]+]][16]; +// CHECK: int32_t [[R:v[0-9]+]] = [[A]][{{v[0-9]+}}]; +// CHECK: uint32_t [[SUM:v[0-9]+]] = {{.*}}[[R]]{{.*}}; // `a[i] = ...a[j]... + ...;` — confirms the get result flows through arith as // a scalar rvalue. The exact cast/paren shape comes from arith.addi's signless // overflow-aware lowering; we only pin the subscript-rvalue flow here. -// CHECK: [[A]][{{v[0-9]+}}] = {{.*}}[[A]][{{v[0-9]+}}]{{.*}}; +// CHECK: [[A]][{{v[0-9]+}}] = {{.*}}[[SUM]]{{.*}}; diff --git a/test/lit/pto/syncfinder_zero_loop_if_probe.pto b/test/lit/pto/syncfinder_zero_loop_if_probe.pto index 6ad298cc0e..d5ebf3db65 100644 --- a/test/lit/pto/syncfinder_zero_loop_if_probe.pto +++ b/test/lit/pto/syncfinder_zero_loop_if_probe.pto @@ -8,8 +8,8 @@ // CHECK: TLOAD // CHECK: set_flag(PIPE_MTE2, PIPE_V, EVENT_ID[[LOOP:[0-9]+]]); // CHECK-NEXT: wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID[[LOOP]]); -// CHECK-NEXT: for (size_t -// CHECK: if ((int32_t) +// CHECK-NEXT: for (int32_t +// CHECK: if ( // CHECK: TMOV // CHECK: } // CHECK: } diff --git a/test/lit/pto/syncfinder_zero_loop_if_probe_gss.pto b/test/lit/pto/syncfinder_zero_loop_if_probe_gss.pto index c700b88324..40649e8699 100644 --- a/test/lit/pto/syncfinder_zero_loop_if_probe_gss.pto +++ b/test/lit/pto/syncfinder_zero_loop_if_probe_gss.pto @@ -8,8 +8,8 @@ // CHECK: TLOAD // CHECK: set_flag(PIPE_MTE2, PIPE_V, EVENT_ID[[LOOP:[0-9]+]]); // CHECK-NEXT: wait_flag(PIPE_MTE2, PIPE_V, EVENT_ID[[LOOP]]); -// CHECK-NEXT: for (size_t -// CHECK: if ((int32_t) +// CHECK-NEXT: for (int32_t +// CHECK: if ( // CHECK: TMOV // CHECK: } // CHECK: } diff --git a/test/lit/pto/tci_i16_emitc.pto b/test/lit/pto/tci_i16_emitc.pto index 4e255b7ad2..83a6be0b75 100644 --- a/test/lit/pto/tci_i16_emitc.pto +++ b/test/lit/pto/tci_i16_emitc.pto @@ -6,7 +6,7 @@ module { %c0 = arith.constant 0 : index %c1 = arith.constant 1 : index %c16 = arith.constant 16 : index - %src = memref.reinterpret_cast %dst to offset: [%c0], sizes: [%c1, %c16], strides: [%c16, %c1] {layout = #pto.layout} : memref<16xi16, #pto.address_space> to memref<1x16xi16, strided<[16, 1], offset: ?>, #pto.address_space> + %src = memref.reinterpret_cast %dst to offset: [%c0], sizes: [1, 16], strides: [16, 1] {layout = #pto.layout} : memref<16xi16, #pto.address_space> to memref<1x16xi16, strided<[16, 1], offset: ?>, #pto.address_space> %tile = pto.alloc_tile : !pto.tile_buf pto.tci ins(%c0_i16 : i16) outs(%tile : !pto.tile_buf) pto.tstore ins(%tile : !pto.tile_buf) outs(%src : memref<1x16xi16, strided<[16, 1], offset: ?>, #pto.address_space>) {layout = #pto.layout, pto.inferred_layout = true} diff --git a/test/lit/pto/tci_ui32_emitc.pto b/test/lit/pto/tci_ui32_emitc.pto index b08c4c1181..0f17452a37 100644 --- a/test/lit/pto/tci_ui32_emitc.pto +++ b/test/lit/pto/tci_ui32_emitc.pto @@ -7,7 +7,7 @@ module { %c0 = arith.constant 0 : index %c1 = arith.constant 1 : index %c32 = arith.constant 32 : index - %src = memref.reinterpret_cast %dst to offset: [%c0], sizes: [%c1, %c32], strides: [%c32, %c1] {layout = #pto.layout} : memref<32xui32, #pto.address_space> to memref<1x32xui32, strided<[32, 1], offset: ?>, #pto.address_space> + %src = memref.reinterpret_cast %dst to offset: [%c0], sizes: [1, 32], strides: [32, 1] {layout = #pto.layout} : memref<32xui32, #pto.address_space> to memref<1x32xui32, strided<[32, 1], offset: ?>, #pto.address_space> %tile = pto.alloc_tile : !pto.tile_buf pto.tci ins(%c5_ui32 : ui32) outs(%tile : !pto.tile_buf) pto.tstore ins(%tile : !pto.tile_buf) outs(%src : memref<1x32xui32, strided<[32, 1], offset: ?>, #pto.address_space>) {layout = #pto.layout, pto.inferred_layout = true} diff --git a/test/lit/pto/tcvt_e2e.pto b/test/lit/pto/tcvt_e2e.pto index fe04fac94a..360fbae337 100644 --- a/test/lit/pto/tcvt_e2e.pto +++ b/test/lit/pto/tcvt_e2e.pto @@ -75,25 +75,25 @@ module attributes {"pto.device-spec" = "Ascend910B3"} { } // A3-LABEL: AICORE void tcvt_f32_to_f16_run( -// A3: RoundMode::CAST_RINT // A3: TLOAD( +// A3: RoundMode::CAST_RINT // A3: TCVT( // A3: TSTORE( // A3-LABEL: AICORE void tcvt_f32_to_bf16_run( -// A3: RoundMode::CAST_ROUND // A3: TLOAD( +// A3: RoundMode::CAST_ROUND // A3: TCVT( // A3: TSTORE( // A5-LABEL: AICORE void tcvt_f32_to_f16_run( -// A5: RoundMode::CAST_RINT // A5: TLOAD( +// A5: RoundMode::CAST_RINT // A5: TCVT( // A5: TSTORE( // A5-LABEL: AICORE void tcvt_f32_to_bf16_run( -// A5: RoundMode::CAST_ROUND // A5: TLOAD( +// A5: RoundMode::CAST_ROUND // A5: TCVT( // A5: TSTORE( diff --git a/test/lit/pto/tcvt_emitc.pto b/test/lit/pto/tcvt_emitc.pto index 1ac8aa0b0e..ae583b1b19 100644 --- a/test/lit/pto/tcvt_emitc.pto +++ b/test/lit/pto/tcvt_emitc.pto @@ -81,13 +81,13 @@ module attributes {"pto.device-spec" = "Ascend910B3"} { // A3: TCVT( // A3-LABEL: AICORE void tcvt_f32_to_i16_sat_off( -// A3: SaturationMode::OFF // A3: RoundMode::CAST_TRUNC +// A3: SaturationMode::OFF // A3: TCVT( // A3-LABEL: AICORE void tcvt_f32_to_i16_sat_on( -// A3: SaturationMode::ON // A3: RoundMode::CAST_ROUND +// A3: SaturationMode::ON // A3: TCVT( // A5-LABEL: AICORE void tcvt_f32_to_f16( @@ -99,11 +99,11 @@ module attributes {"pto.device-spec" = "Ascend910B3"} { // A5: TCVT( // A5-LABEL: AICORE void tcvt_f32_to_i16_sat_off( -// A5: SaturationMode::OFF // A5: RoundMode::CAST_TRUNC +// A5: SaturationMode::OFF // A5: TCVT( // A5-LABEL: AICORE void tcvt_f32_to_i16_sat_on( -// A5: SaturationMode::ON // A5: RoundMode::CAST_ROUND +// A5: SaturationMode::ON // A5: TCVT( diff --git a/test/lit/pto/tprint_alloc_tile_no_rebind.pto b/test/lit/pto/tprint_alloc_tile_no_rebind.pto index a3cb04b08b..bd54cf435d 100644 --- a/test/lit/pto/tprint_alloc_tile_no_rebind.pto +++ b/test/lit/pto/tprint_alloc_tile_no_rebind.pto @@ -14,7 +14,7 @@ module { // CHECK-LABEL: __global__ AICORE void print_kernel() { // CHECK: Tile [[TILE:v[0-9]+]]; -// CHECK: TASSIGN([[TILE]], [[ADDR:v[0-9]+]]); +// CHECK: TASSIGN([[TILE]], (uint64_t) {{v[0-9]+}}); // CHECK-NOT: TASSIGN( // CHECK-NOT: .data() // CHECK-NOT: reinterpret_cast diff --git a/test/lit/pto/tpush_tpop_emitc.pto b/test/lit/pto/tpush_tpop_emitc.pto index f976c8d677..5e830c26cf 100644 --- a/test/lit/pto/tpush_tpop_emitc.pto +++ b/test/lit/pto/tpush_tpop_emitc.pto @@ -32,18 +32,18 @@ module { } // A3-LABEL: AICORE void cube_push_gm( -// A3: const int32_t {{v[0-9]+}} = 0; -// A3: const int64_t {{v[0-9]+}} = 0; // A3: #if defined(__DAV_CUBE__) +// A3-DAG: const int32_t {{v[0-9]+}} = 0; +// A3-DAG: const int64_t {{v[0-9]+}} = 0; // A3: auto {{v[0-9]+}} = TPipe<0, Direction::DIR_C2V, 1024, 8, 8, true>( // A3: TPUSH, Tile, TileSplitAxis::TILE_NO_SPLIT>( // A3: #endif // __DAV_CUBE__ // A3-LABEL: AICORE void vector_pop_gm( -// A3: const int32_t {{v[0-9]+}} = 0; // A3: #if defined(__DAV_VEC__) // A3: set_mask_norm(); // A3: set_vector_mask(-1, -1); +// A3: const int32_t {{v[0-9]+}} = 0; // A3: auto {{v[0-9]+}} = TPipe<0, Direction::DIR_C2V, 1024, 8, 8, false>( // A3: Tile {{v[0-9]+}}; // A3: TPOP, Tile, TileSplitAxis::TILE_UP_DOWN>( diff --git a/test/lit/pto/tpush_tpop_globaltensor_split_half_slot_a3.pto b/test/lit/pto/tpush_tpop_globaltensor_split_half_slot_a3.pto index b4360efea1..b2a06d2dbe 100644 --- a/test/lit/pto/tpush_tpop_globaltensor_split_half_slot_a3.pto +++ b/test/lit/pto/tpush_tpop_globaltensor_split_half_slot_a3.pto @@ -63,7 +63,7 @@ module { // CHECK-LABEL: AICORE void vector_kernel // CHECK: TPipe<0, Direction::DIR_C2V, 262144, 8, 8, false> // CHECK: TPOP, GlobalTensor, pto::Stride<65536, 65536, 16384, 128, 1>, pto::Layout::ND>, TileSplitAxis::TILE_UP_DOWN> -// CHECK: GlobalTensor, pto::Stride<65536, 65536, 16384, 128, 1>, pto::Layout::ND> +// CHECK: GlobalTensor, pto::Stride<32768, 32768, 8192, 128, 1>, pto::Layout::ND> // CHECK: TFREE, GlobalTensor, pto::Stride<65536, 65536, 16384, 128, 1>, pto::Layout::ND>, TileSplitAxis::TILE_UP_DOWN> // CHECK-LABEL: __global__ AICORE void call_both // CHECK: set_ffts_base_addr diff --git a/test/lit/pto/tsort_emitc.pto b/test/lit/pto/tsort_emitc.pto index 4fe7206823..8bd2e442c8 100644 --- a/test/lit/pto/tsort_emitc.pto +++ b/test/lit/pto/tsort_emitc.pto @@ -24,5 +24,5 @@ module { // VIEW-SAME: {descending = true} // EMITC-DAG: const bool [[ASC:v[0-9]+]] = false; // EMITC-DAG: const bool [[DESC:v[0-9]+]] = true; -// EMITC: TSORT([[VDST_ASC:v[0-9]+]], [[VIDX_ASC:v[0-9]+]], [[VSRC:v[0-9]+]], [[ASC]]); -// EMITC: TSORT([[VDST_DESC:v[0-9]+]], [[VIDX_DESC:v[0-9]+]], [[VSRC_DESC:v[0-9]+]], [[DESC]]); +// EMITC-DAG: TSORT([[VDST_ASC:v[0-9]+]], [[VIDX_ASC:v[0-9]+]], [[VSRC:v[0-9]+]], [[ASC]]); +// EMITC-DAG: TSORT([[VDST_DESC:v[0-9]+]], [[VIDX_DESC:v[0-9]+]], [[VSRC_DESC:v[0-9]+]], [[DESC]]); From 41269fabc4dd3336eaa5761297e46943d9da9b5c Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Tue, 11 Aug 2026 20:00:26 +0800 Subject: [PATCH 03/32] ci: install LLVM 23 nanobind dependency --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbf165f5cd..6712fa0068 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,7 @@ jobs: libedit-dev zlib1g-dev libxml2-dev libzstd-dev python3 -m pip install --upgrade pip # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x. - python3 -m pip install 'pybind11<3' numpy + python3 -m pip install 'pybind11<3' nanobind numpy - name: Define payload paths shell: bash From 907deeeb935027f5e3151ca2cb864fda34394d57 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Tue, 11 Aug 2026 23:14:42 +0800 Subject: [PATCH 04/32] ci: isolate PTOAS from bundled LLVM PTO dialect --- .github/workflows/build_wheel.yml | 7 ++++--- .github/workflows/build_wheel_mac.yml | 7 ++++--- .github/workflows/ci.yml | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 6d01163c3b..a37472d5f2 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -101,7 +101,7 @@ jobs: uses: actions/cache/restore@v4 with: path: /llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v3 + key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 - name: Prepare LLVM source run: | @@ -122,7 +122,7 @@ jobs: echo "LLVM cache miss while running on PR/release." echo "PR/release runs are cache-consumers only and should reuse cache generated on the default branch." echo "Please run this workflow on the default branch first to populate cache key:" - echo "llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v3" + echo "llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1" - name: Build LLVM/MLIR if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -131,6 +131,7 @@ jobs: cd $LLVM_SOURCE_DIR cmake -C "$PTO_SOURCE_DIR/cmake/LinuxHardeningCache.cmake" -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ + -DMLIR_ENABLE_LINXISA_PTO=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=${PY_PATH}/bin/python \ @@ -145,7 +146,7 @@ jobs: uses: actions/cache/save@v4 with: path: /llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-hardening-v3 + key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 - name: Build PTOAS run: | diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index 13b00fa38d..f966c9a8fe 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -105,7 +105,7 @@ jobs: uses: actions/cache/restore@v4 with: path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v2 + key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 - name: Prepare LLVM source run: | @@ -126,7 +126,7 @@ jobs: echo "LLVM cache miss while running on PR/release." echo "PR/release runs are cache-consumers only and should reuse cache generated on the default branch." echo "Please run this workflow on the default branch first to populate cache key:" - echo "llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v2" + echo "llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1" - name: Build LLVM/MLIR if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -134,6 +134,7 @@ jobs: cd $LLVM_SOURCE_DIR cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ + -DMLIR_ENABLE_LINXISA_PTO=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=$(which python) \ @@ -148,7 +149,7 @@ jobs: uses: actions/cache/save@v4 with: path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-v2 + key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 - name: Build PTOAS run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6712fa0068..415aaaf3cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: with: path: | llvm-project/llvm/build-shared - key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-hardening-v2 + key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-pto-off-v1 - name: Prepare LLVM source (no rebuild) run: | @@ -169,6 +169,7 @@ jobs: cd llvm-project cmake -C "${GITHUB_WORKSPACE}/cmake/LinuxHardeningCache.cmake" -G Ninja -S llvm -B llvm/build-shared \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ + -DMLIR_ENABLE_LINXISA_PTO=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=python3 \ @@ -185,7 +186,7 @@ jobs: with: path: | llvm-project/llvm/build-shared - key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-hardening-v2 + key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-pto-off-v1 - name: Build PTOAS run: | From e1743e5e690abee3d24a07705cab7763f6b7ea1e Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Tue, 11 Aug 2026 23:44:49 +0800 Subject: [PATCH 05/32] ci: exclude upstream LLVM test targets --- .github/workflows/build_wheel.yml | 7 +++++-- .github/workflows/build_wheel_mac.yml | 9 ++++++--- .github/workflows/ci.yml | 7 +++++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index a37472d5f2..b818e863a7 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -101,7 +101,7 @@ jobs: uses: actions/cache/restore@v4 with: path: /llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 + key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2 - name: Prepare LLVM source run: | @@ -132,6 +132,9 @@ jobs: cmake -C "$PTO_SOURCE_DIR/cmake/LinuxHardeningCache.cmake" -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ -DMLIR_ENABLE_LINXISA_PTO=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=${PY_PATH}/bin/python \ @@ -146,7 +149,7 @@ jobs: uses: actions/cache/save@v4 with: path: /llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 + key: llvm-${{ env.LLVM_COMMIT }}-manylinux_2_34-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2 - name: Build PTOAS run: | diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index f966c9a8fe..c9ea0d3749 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -105,7 +105,7 @@ jobs: uses: actions/cache/restore@v4 with: path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 + key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2 - name: Prepare LLVM source run: | @@ -126,7 +126,7 @@ jobs: echo "LLVM cache miss while running on PR/release." echo "PR/release runs are cache-consumers only and should reuse cache generated on the default branch." echo "Please run this workflow on the default branch first to populate cache key:" - echo "llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1" + echo "llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2" - name: Build LLVM/MLIR if: steps.cache-llvm.outputs.cache-hit != 'true' @@ -135,6 +135,9 @@ jobs: cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ -DMLIR_ENABLE_LINXISA_PTO=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=$(which python) \ @@ -149,7 +152,7 @@ jobs: uses: actions/cache/save@v4 with: path: ${{ runner.temp }}/llvm-workspace/llvm-project/build-shared - key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v1 + key: llvm-${{ env.LLVM_COMMIT }}-${{ steps.runner-meta.outputs.label }}-${{ matrix.arch }}-${{ matrix.python }}-pto-off-v2 - name: Build PTOAS run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 415aaaf3cc..b10d472a59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,7 +145,7 @@ jobs: with: path: | llvm-project/llvm/build-shared - key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-pto-off-v1 + key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-pto-off-v2 - name: Prepare LLVM source (no rebuild) run: | @@ -170,6 +170,9 @@ jobs: cmake -C "${GITHUB_WORKSPACE}/cmake/LinuxHardeningCache.cmake" -G Ninja -S llvm -B llvm/build-shared \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ -DMLIR_ENABLE_LINXISA_PTO=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ + -DLLVM_INCLUDE_EXAMPLES=OFF \ + -DLLVM_INCLUDE_BENCHMARKS=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=python3 \ @@ -186,7 +189,7 @@ jobs: with: path: | llvm-project/llvm/build-shared - key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-pto-off-v1 + key: llvm-${{ runner.os }}-${{ env.LLVM_COMMIT }}-shared-mlirpy-pto-off-v2 - name: Build PTOAS run: | From fb7e4bfc9b33ee30e1b1aa94d31b816663879424 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Tue, 11 Aug 2026 23:57:40 +0800 Subject: [PATCH 06/32] ci: pin optional PTO LLVM patch --- .github/workflows/build_wheel.yml | 2 +- .github/workflows/build_wheel_mac.yml | 2 +- .github/workflows/ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index b818e863a7..12310cf378 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: be1fc2451936a713e2a15620d5e7f8fe0a73688e + LLVM_COMMIT: efd3cc5754e24ffe5d7ee4b77762c7f45c8b0196 jobs: build_wheel: diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index c9ea0d3749..ded83f13c9 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: be1fc2451936a713e2a15620d5e7f8fe0a73688e + LLVM_COMMIT: efd3cc5754e24ffe5d7ee4b77762c7f45c8b0196 jobs: build_wheel: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b10d472a59..911d2d5275 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: be1fc2451936a713e2a15620d5e7f8fe0a73688e + LLVM_COMMIT: efd3cc5754e24ffe5d7ee4b77762c7f45c8b0196 LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core From c80734ec051c600fc5dbeceb7787471a707d8eb0 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Wed, 12 Aug 2026 01:56:08 +0800 Subject: [PATCH 07/32] ci: pin LLVM PTO-off header fix Signed-off-by: RuoyuZhou --- .github/workflows/build_wheel.yml | 2 +- .github/workflows/build_wheel_mac.yml | 2 +- .github/workflows/ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 12310cf378..523280b141 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: efd3cc5754e24ffe5d7ee4b77762c7f45c8b0196 + LLVM_COMMIT: 05fe79770059617f6edbeac7ded144f5873224e0 jobs: build_wheel: diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index ded83f13c9..fb7ac78d32 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: efd3cc5754e24ffe5d7ee4b77762c7f45c8b0196 + LLVM_COMMIT: 05fe79770059617f6edbeac7ded144f5873224e0 jobs: build_wheel: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 911d2d5275..299bc74924 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: efd3cc5754e24ffe5d7ee4b77762c7f45c8b0196 + LLVM_COMMIT: 05fe79770059617f6edbeac7ded144f5873224e0 LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core From 6a520f0408d3096ffa83724f4da8b834342644a6 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Wed, 12 Aug 2026 03:31:59 +0800 Subject: [PATCH 08/32] ci: install Python 3.10 stubgen dependency --- .github/workflows/build_wheel.yml | 2 +- .github/workflows/build_wheel_mac.yml | 2 +- .github/workflows/ci.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 523280b141..ab6960044a 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -86,7 +86,7 @@ jobs: export PATH="${PY_PATH}/bin:$PATH" # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x # (see pybind11 static_assert on def_property + keep_alive). - pip install --no-cache-dir numpy 'pybind11<3' nanobind setuptools wheel auditwheel + pip install --no-cache-dir numpy 'pybind11<3' nanobind typing_extensions setuptools wheel auditwheel - name: Set build directories run: | diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index fb7ac78d32..81590fab3d 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -90,7 +90,7 @@ jobs: run: | # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x # (see pybind11 static_assert on def_property + keep_alive). - pip install --no-cache-dir numpy 'pybind11<3' nanobind setuptools wheel delocate + pip install --no-cache-dir numpy 'pybind11<3' nanobind typing_extensions setuptools wheel delocate - name: Set build directories run: | diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 299bc74924..5b0b899a91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,7 +117,7 @@ jobs: libedit-dev zlib1g-dev libxml2-dev libzstd-dev python3 -m pip install --upgrade pip # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x. - python3 -m pip install 'pybind11<3' nanobind numpy + python3 -m pip install 'pybind11<3' nanobind typing_extensions numpy - name: Define payload paths shell: bash From bbaf8af181a370746c622559a3e48b5c1874ff1d Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Wed, 12 Aug 2026 05:41:09 +0800 Subject: [PATCH 09/32] build: migrate PTO Python binding to nanobind --- .github/workflows/build_wheel.yml | 9 ++- .github/workflows/build_wheel_mac.yml | 9 ++- .github/workflows/ci.yml | 10 +-- CMakeLists.txt | 5 +- README.md | 21 +++--- docker/Dockerfile | 5 +- docs/designs/ci-board-validation-guide.md | 5 +- lib/Bindings/Python/CMakeLists.txt | 14 ++-- lib/Bindings/Python/PTOModule.cpp | 79 ++++++++++++----------- 9 files changed, 83 insertions(+), 74 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index ab6960044a..32a772de85 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 05fe79770059617f6edbeac7ded144f5873224e0 + LLVM_COMMIT: 9fb4f7aa89ee22541bfae288e2085ada266ffd75 jobs: build_wheel: @@ -84,9 +84,7 @@ jobs: - name: Install Python dependencies run: | export PATH="${PY_PATH}/bin:$PATH" - # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x - # (see pybind11 static_assert on def_property + keep_alive). - pip install --no-cache-dir numpy 'pybind11<3' nanobind typing_extensions setuptools wheel auditwheel + pip install --no-cache-dir numpy nanobind typing_extensions setuptools wheel auditwheel - name: Set build directories run: | @@ -162,8 +160,9 @@ jobs: -DMLIR_DIR=$LLVM_BUILD_DIR/lib/cmake/mlir \ -DPython3_ROOT_DIR=${PY_PATH} \ -DPython3_EXECUTABLE=${PY_PATH}/bin/python \ + -DPython_EXECUTABLE=${PY_PATH}/bin/python \ -DPython3_FIND_STRATEGY=LOCATION \ - -Dpybind11_DIR=$(${PY_PATH}/bin/python -m pybind11 --cmakedir) \ + -Dnanobind_DIR=$(${PY_PATH}/bin/python -m nanobind --cmake_dir) \ -DMLIR_PYTHON_PACKAGE_DIR=${LLVM_BUILD_DIR}/tools/mlir/python_packages/mlir_core \ -DPTOAS_RELEASE_VERSION_OVERRIDE=${PTOAS_VERSION} \ -DPTOAS_ENABLE_WERROR=OFF \ diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index 81590fab3d..bcd83b68f3 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 05fe79770059617f6edbeac7ded144f5873224e0 + LLVM_COMMIT: 9fb4f7aa89ee22541bfae288e2085ada266ffd75 jobs: build_wheel: @@ -88,9 +88,7 @@ jobs: - name: Install Python dependencies run: | - # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x - # (see pybind11 static_assert on def_property + keep_alive). - pip install --no-cache-dir numpy 'pybind11<3' nanobind typing_extensions setuptools wheel delocate + pip install --no-cache-dir numpy nanobind typing_extensions setuptools wheel delocate - name: Set build directories run: | @@ -164,8 +162,9 @@ jobs: -DMLIR_DIR=$LLVM_BUILD_DIR/lib/cmake/mlir \ -DPython3_ROOT_DIR=${PY_PATH} \ -DPython3_EXECUTABLE=$(which python) \ + -DPython_EXECUTABLE=$(which python) \ -DPython3_FIND_STRATEGY=LOCATION \ - -Dpybind11_DIR=$(python -m pybind11 --cmakedir) \ + -Dnanobind_DIR=$(python -m nanobind --cmake_dir) \ -DMLIR_PYTHON_PACKAGE_DIR=${LLVM_BUILD_DIR}/tools/mlir/python_packages/mlir_core \ -DPTOAS_RELEASE_VERSION_OVERRIDE=${PTOAS_VERSION} \ -DPTOAS_ENABLE_WERROR=OFF \ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b0b899a91..087138a1fc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 05fe79770059617f6edbeac7ded144f5873224e0 + LLVM_COMMIT: 9fb4f7aa89ee22541bfae288e2085ada266ffd75 LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core @@ -116,8 +116,7 @@ jobs: clang lld \ libedit-dev zlib1g-dev libxml2-dev libzstd-dev python3 -m pip install --upgrade pip - # LLVM/MLIR Python bindings are not yet compatible with pybind11 3.x. - python3 -m pip install 'pybind11<3' nanobind typing_extensions numpy + python3 -m pip install nanobind typing_extensions numpy - name: Define payload paths shell: bash @@ -193,13 +192,14 @@ jobs: - name: Build PTOAS run: | - export PYBIND11_CMAKE_DIR="$(python3 -m pybind11 --cmakedir)" + export NANOBIND_CMAKE_DIR="$(python3 -m nanobind --cmake_dir)" cmake -C "${GITHUB_WORKSPACE}/cmake/LinuxHardeningCache.cmake" -G Ninja -S . -B build \ -DLLVM_DIR="${LLVM_DIR}/lib/cmake/llvm" \ -DMLIR_DIR="${LLVM_DIR}/lib/cmake/mlir" \ -DPython3_EXECUTABLE=python3 \ + -DPython_EXECUTABLE=python3 \ -DPython3_FIND_STRATEGY=LOCATION \ - -Dpybind11_DIR="${PYBIND11_CMAKE_DIR}" \ + -Dnanobind_DIR="${NANOBIND_CMAKE_DIR}" \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DMLIR_PYTHON_PACKAGE_DIR="${LLVM_DIR}/tools/mlir/python_packages/mlir_core" \ -DPTOAS_ENABLE_WERROR=OFF \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 3685ae4529..36d82bf65f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,7 +109,10 @@ option(PTO_ENABLE_PYTHON_BINDING "Enable Python bindings" ON) if(PTO_ENABLE_PYTHON_BINDING) find_package(Python3 COMPONENTS Interpreter Development.Module REQUIRED) - find_package(pybind11 CONFIG REQUIRED) + set(Python_EXECUTABLE "${Python3_EXECUTABLE}" CACHE FILEPATH + "Python interpreter used by nanobind") + find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) + find_package(nanobind CONFIG REQUIRED) # 设置 Python 扩展的输出目录,方便调试 set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/python/pto) diff --git a/README.md b/README.md index d9007c9603..730f52ee1d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **ptoas** (`ptoas`) 是一个基于 LinxISA 受控 **LLVM/MLIR** (release `linxisa-v0.58.0`,commit -`be1fc2451936a713e2a15620d5e7f8fe0a73688e`)构建的专用编译器工具链, +`9fb4f7aa89ee22541bfae288e2085ada266ffd75`)构建的专用编译器工具链, 专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 作为连接上层 AI 框架与底层各类NPU/GPGPU/CPU硬件,`ptoas` 采用 **Out-of-Tree** 架构构建,提供了完整的 C++ 与 Python 接口,主要职责包括: @@ -41,7 +41,7 @@ PTOAS/ ## 3. 构建指南 (Build Instructions) ⚠️ **重要提示**:本项目严格依赖 LinxISA LLVM -`be1fc2451936a713e2a15620d5e7f8fe0a73688e`。该 SHA 对应 +`9fb4f7aa89ee22541bfae288e2085ada266ffd75`。该 SHA 对应 `linxisa-v0.58.0`,不要替换为同名上游 LLVM tag。 @@ -74,16 +74,12 @@ mkdir -p $WORKSPACE_DIR * **Compiler**: GCC >= 9 或 Clang (支持 C++17) * **Build System**: CMake >= 3.20, Ninja * **Python**: 3.8+ -* **Python Packages**: `pybind11`, `numpy` +* **Python Packages**: `nanobind`, `numpy` ```bash -python3 -m pip install pybind11==2.12.0 numpy +python3 -m pip install nanobind numpy ``` -> 说明:当前 LLVM/MLIR Python 绑定与 `pybind11` 3.x 不兼容。 -> 如果编译 LLVM 时遇到 `def_property family does not currently support keep_alive` 等报错, -> 请先执行上面的降级命令。 - ### 3.2 第一步:构建 LLVM/MLIR (Dependency) @@ -96,7 +92,7 @@ python3 -m pip install pybind11==2.12.0 numpy cd $WORKSPACE_DIR git clone https://github.com/LinxISA/llvm-project.git cd $LLVM_SOURCE_DIR -git checkout --detach be1fc2451936a713e2a15620d5e7f8fe0a73688e +git checkout --detach 9fb4f7aa89ee22541bfae288e2085ada266ffd75 # 2. 配置 CMake (构建动态库并启用 Python 绑定) cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ @@ -122,8 +118,8 @@ cd $WORKSPACE_DIR git clone https://gitcode.com/cann/pto-as.git PTOAS cd $PTO_SOURCE_DIR -# 2. 获取 pybind11 的 CMake 路径 -export PYBIND11_CMAKE_DIR=$(python3 -m pybind11 --cmakedir) +# 2. 获取 nanobind 的 CMake 路径 +export NANOBIND_CMAKE_DIR=$(python3 -m nanobind --cmake_dir) # 3. 配置 CMake # 注意:此处直接使用了 3.0 章节中定义的变量,无需手动修改 @@ -133,8 +129,9 @@ cmake -G Ninja \ -DLLVM_DIR=$LLVM_BUILD_DIR/lib/cmake/llvm \ -DMLIR_DIR=$LLVM_BUILD_DIR/lib/cmake/mlir \ -DPython3_EXECUTABLE=$(which python3) \ + -DPython_EXECUTABLE=$(which python3) \ -DPython3_FIND_STRATEGY=LOCATION \ - -Dpybind11_DIR="${PYBIND11_CMAKE_DIR}" \ + -Dnanobind_DIR="${NANOBIND_CMAKE_DIR}" \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DMLIR_PYTHON_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core \ -DPTOAS_ENABLE_WERROR=OFF \ diff --git a/docker/Dockerfile b/docker/Dockerfile index efc6b84fab..6bcf9f7889 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -19,7 +19,7 @@ ENV PATH="${PY_PATH}/bin:${PATH}" # dependency RUN dnf install -y ninja-build cmake git ccache gcc-c++ lld zip binutils patchelf chrpath && dnf clean all -RUN pip install --no-cache-dir numpy pybind11 nanobind setuptools wheel auditwheel +RUN pip install --no-cache-dir numpy nanobind setuptools wheel auditwheel COPY cmake/LinuxHardeningCache.cmake /tmp/LinuxHardeningCache.cmake @@ -59,8 +59,9 @@ RUN cmake -C /tmp/LinuxHardeningCache.cmake -G Ninja \ -DMLIR_DIR=$LLVM_BUILD_DIR/lib/cmake/mlir \ -DPython3_ROOT_DIR=${PY_PATH} \ -DPython3_EXECUTABLE=${PY_PATH}/bin/python \ + -DPython_EXECUTABLE=${PY_PATH}/bin/python \ -DPython3_FIND_STRATEGY=LOCATION \ - -Dpybind11_DIR=$(python -m pybind11 --cmakedir) \ + -Dnanobind_DIR=$(python -m nanobind --cmake_dir) \ -DMLIR_PYTHON_PACKAGE_DIR=${LLVM_BUILD_DIR}/tools/mlir/python_packages/mlir_core \ -DCMAKE_INSTALL_PREFIX=${PTO_INSTALL_DIR} diff --git a/docs/designs/ci-board-validation-guide.md b/docs/designs/ci-board-validation-guide.md index b87feefd9f..0361bd3641 100644 --- a/docs/designs/ci-board-validation-guide.md +++ b/docs/designs/ci-board-validation-guide.md @@ -103,14 +103,15 @@ ninja -C llvm/build-shared ```bash export LLVM_DIR=$PWD/llvm-project/llvm/build-shared export PTO_INSTALL_DIR=$PWD/install -export PYBIND11_CMAKE_DIR="$(python3 -m pybind11 --cmakedir)" +export NANOBIND_CMAKE_DIR="$(python3 -m nanobind --cmake_dir)" cmake -G Ninja -S . -B build \ -DLLVM_DIR="$LLVM_DIR/lib/cmake/llvm" \ -DMLIR_DIR="$LLVM_DIR/lib/cmake/mlir" \ -DPython3_EXECUTABLE=python3 \ + -DPython_EXECUTABLE=python3 \ -DPython3_FIND_STRATEGY=LOCATION \ - -Dpybind11_DIR="$PYBIND11_CMAKE_DIR" \ + -Dnanobind_DIR="$NANOBIND_CMAKE_DIR" \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DMLIR_PYTHON_PACKAGE_DIR="$LLVM_DIR/tools/mlir/python_packages/mlir_core" \ -DCMAKE_INSTALL_PREFIX="$PTO_INSTALL_DIR" \ diff --git a/lib/Bindings/Python/CMakeLists.txt b/lib/Bindings/Python/CMakeLists.txt index e9e32ba983..fc43fbb251 100644 --- a/lib/Bindings/Python/CMakeLists.txt +++ b/lib/Bindings/Python/CMakeLists.txt @@ -13,17 +13,24 @@ # lib/Bindings/Python/CMakeLists.txt -find_package(Python3 COMPONENTS Interpreter Development.Module REQUIRED) -find_package(pybind11 CONFIG REQUIRED) +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) +find_package(nanobind CONFIG REQUIRED) include(TableGen) include(AddMLIR) # ---- 1) Python native extension: mlir._mlir_libs._pto ---- -pybind11_add_module(_pto MODULE +nanobind_add_module(_pto + NB_DOMAIN mlir + FREE_THREADED + NB_SHARED PTOModule.cpp ) +target_compile_definitions(_pto PRIVATE + MLIR_BINDINGS_PYTHON_DOMAIN=mlir +) + target_include_directories(_pto PRIVATE ${CMAKE_SOURCE_DIR}/include ) @@ -101,4 +108,3 @@ add_custom_command(TARGET _pto POST_BUILD "${CMAKE_BINARY_DIR}/python/mlir/_mlir_libs" VERBATIM ) - diff --git a/lib/Bindings/Python/PTOModule.cpp b/lib/Bindings/Python/PTOModule.cpp index e25fc6244d..274a1038b0 100644 --- a/lib/Bindings/Python/PTOModule.cpp +++ b/lib/Bindings/Python/PTOModule.cpp @@ -8,16 +8,15 @@ //===- DialectPTO.cpp -----------------------------------------------------===// // -// Python bindings for the PTO dialect types (pybind11 version). +// Python bindings for the PTO dialect types. // -// This file is intended to be built via declare_mlir_python_extension(...) -// with PYTHON_BINDINGS_LIBRARY pybind11, and linked with MLIRCAPIPTO. +// This extension uses MLIR's nanobind adaptors and links with MLIRCAPIPTO. // //===----------------------------------------------------------------------===// -#include "pybind11/pybind11.h" -#include "pybind11/stl.h" -#include "mlir/Bindings/Python/PybindAdaptors.h" +#include "mlir/Bindings/Python/Nanobind.h" +#include "mlir/Bindings/Python/NanobindAdaptors.h" +#include "nanobind/stl/vector.h" #include "pto-c/Dialect/PTO.h" #include "mlir-c/IR.h" #include "PTO/IR/PTO.h" @@ -25,12 +24,13 @@ #include "mlir-c/BuiltinAttributes.h" #include "mlir-c/Support.h" #include "mlir/IR/BuiltinTypes.h" -namespace py = pybind11; -using namespace mlir::python::adaptors; +namespace py = nanobind; +using namespace nanobind::literals; +using namespace mlir::python::nanobind_adaptors; static std::vector toInt64Vector(const py::sequence &seq) { std::vector out; - out.reserve(seq.size()); + out.reserve(py::len(seq)); for (py::handle h : seq) out.push_back(py::cast(h)); return out; @@ -38,13 +38,13 @@ static std::vector toInt64Vector(const py::sequence &seq) { static std::vector toShapeVectorOrDynamicRank(py::object shapeOrRank) { if (py::isinstance(shapeOrRank)) { - auto rank = shapeOrRank.cast(); + auto rank = py::cast(shapeOrRank); if (rank < 0) throw py::value_error("rank must be non-negative"); return std::vector(static_cast(rank), mlir::ShapedType::kDynamic); } - return toInt64Vector(shapeOrRank.cast()); + return toInt64Vector(py::cast(shapeOrRank)); } static MlirContext inferContextFromElementType(MlirContext context, @@ -63,13 +63,13 @@ static py::list shapeToPyList(const int64_t *data, intptr_t n) { return lst; } -void populatePTODialectSubmodule(pybind11::module &m); -void populatePTODialectSubmodule(pybind11::module &m) { +void populatePTODialectSubmodule(nanobind::module_ &m); +void populatePTODialectSubmodule(nanobind::module_ &m) { (void)m; } -static void bindPTOModule(pybind11::module &m) { - m.doc() = "PTO dialect Python bindings (pybind11)."; +static void bindPTOModule(nanobind::module_ &m) { + m.doc() = "PTO dialect Python bindings."; // -------------------------------------------------------------------------- // Dialect registration helper @@ -376,7 +376,7 @@ static void bindPTOModule(pybind11::module &m) { v = py::cast(value); } else { // enum: pto.AddressSpace.UB -> 转成 int - v = py::cast(value.attr("value").cast()); + v = py::cast(value.attr("value")); } MlirAttribute a = mlirPTOAddressSpaceAttrGet(context, v); return cls.attr("__call__")(a); @@ -396,10 +396,10 @@ static void bindPTOModule(pybind11::module &m) { [](py::object cls, py::object value, MlirContext ctx) -> py::object { int32_t v = 0; if (py::isinstance(value)) { - v = value.cast(); + v = py::cast(value); } else if (py::hasattr(value, "value")) { // 通用:py::enum_ 通常有 .value - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("RoundModeAttr.get expects int or RoundMode enum"); } @@ -424,9 +424,9 @@ static void bindPTOModule(pybind11::module &m) { [](py::object cls, py::object value, MlirContext ctx) -> py::object { int32_t v = 0; if (py::isinstance(value)) { - v = value.cast(); + v = py::cast(value); } else if (py::hasattr(value, "value")) { - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("SaturationModeAttr.get expects int or SaturationMode enum"); } @@ -451,9 +451,9 @@ static void bindPTOModule(pybind11::module &m) { [](py::object cls, py::object value, MlirContext ctx) -> py::object { int32_t v = 0; if (py::isinstance(value)) { - v = value.cast(); + v = py::cast(value); } else if (py::hasattr(value, "value")) { - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("PipeAttr.get expects int or PIPE enum"); } @@ -477,9 +477,9 @@ static void bindPTOModule(pybind11::module &m) { [](py::object cls, py::object value, MlirContext ctx) -> py::object { int32_t v = 0; if (py::isinstance(value)) { - v = value.cast(); + v = py::cast(value); } else if (py::hasattr(value, "value")) { - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("LayoutAttr.get expects int or Layout enum"); } @@ -518,7 +518,7 @@ static void bindPTOModule(pybind11::module &m) { if (py::isinstance(value)) { v = py::cast(value); } else if (py::hasattr(value, "value")) { - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("SyncOpTypeAttr.get expects int or SyncOpType enum"); } @@ -543,7 +543,7 @@ static void bindPTOModule(pybind11::module &m) { if (py::isinstance(value)) { v = py::cast(value); } else if (py::hasattr(value, "value")) { - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("EventAttr.get expects int or EVENT enum"); } @@ -573,7 +573,7 @@ static void bindPTOModule(pybind11::module &m) { if (py::isinstance(value)) { v = py::cast(value); } else if (py::hasattr(value, "value")) { - v = value.attr("value").cast(); + v = py::cast(value.attr("value")); } else { throw std::runtime_error("QuantTypeAttr.get expects int or QuantType enum"); } @@ -598,7 +598,8 @@ static void bindPTOModule(pybind11::module &m) { MlirAttribute a{nullptr}; if (py::isinstance(value, maskPatternEnumType)) { auto v = - static_cast(value.attr("value").cast()); + static_cast( + py::cast(value.attr("value"))); a = mlirPTOMaskPatternAttrGetEnum(ctx, v); } else if (py::isinstance(value)) { int32_t v = py::cast(value); @@ -841,12 +842,13 @@ static void bindPTOModule(pybind11::module &m) { if (!compactModeObj.is_none()) { if (py::isinstance(compactModeObj)) { compactMode = mlirPTOCompactModeAttrGet( - ctx, compactModeObj.cast()); + ctx, py::cast(compactModeObj)); } else if (py::hasattr(compactModeObj, "value")) { compactMode = mlirPTOCompactModeAttrGet( - ctx, compactModeObj.attr("value").cast()); + ctx, + py::cast(compactModeObj.attr("value"))); } else { - compactMode = compactModeObj.cast(); + compactMode = py::cast(compactModeObj); } } MlirAttribute a = mlirPTOTileBufConfigAttrGetWithCompactMode( @@ -882,17 +884,18 @@ static void bindPTOModule(pybind11::module &m) { if (!validShapeObj.is_none()) { // 支持 valid_shape 为 list[int] 或 list[Optional[int]] - py::list lst = validShapeObj.cast(); - if ((size_t)lst.size() != shape.size()) { + py::list lst = py::cast(validShapeObj); + const size_t listSize = py::len(lst); + if (listSize != shape.size()) { throw std::runtime_error("valid_shape rank must match shape rank"); } - validShape.resize(lst.size()); - for (py::ssize_t i = 0; i < static_cast(lst.size()); ++i) { + validShape.resize(listSize); + for (size_t i = 0; i < listSize; ++i) { py::object e = lst[i]; if (e.is_none()) { validShape[i] = -1; // None -> dynamic } else { - validShape[i] = e.cast(); + validShape[i] = py::cast(e); } } } @@ -900,7 +903,7 @@ static void bindPTOModule(pybind11::module &m) { // 2) 调 CAPI MlirType ty; if (!configObj.is_none()) { - MlirAttribute cfg = configObj.cast(); + MlirAttribute cfg = py::cast(configObj); ty = mlirPTOTileBufTypeGetWithValidShapeAndConfig( ctx, (intptr_t)shape.size(), shape.data(), @@ -936,6 +939,6 @@ static void bindPTOModule(pybind11::module &m) { populatePTODialectSubmodule(m); } -PYBIND11_MODULE(_pto, m) { +NB_MODULE(_pto, m) { bindPTOModule(m); } From 40feccec0335c6c3af0855ea6afdb74213a0b907 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Wed, 12 Aug 2026 07:38:06 +0800 Subject: [PATCH 10/32] build: allow CPython ABI resolution in wheel runtime --- .github/workflows/build_wheel.yml | 3 ++ .github/workflows/ci.yml | 4 +++ CMakeLists.txt | 5 +++ lib/Bindings/Python/CMakeLists.txt | 9 ++++++ tools/check_python_binding_link_contract.py | 36 +++++++++++++++++++++ 5 files changed, 57 insertions(+) create mode 100644 tools/check_python_binding_link_contract.py diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 32a772de85..f88351ce43 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -56,6 +56,9 @@ jobs: echo "PY_VER=$PY_VER" >> $GITHUB_ENV echo "PY_PATH=/opt/python/$PY_VER" >> $GITHUB_ENV + - name: Validate Python binding link contract + run: ${PY_PATH}/bin/python tools/check_python_binding_link_contract.py --ptoas-root . + - name: Compute PTOAS CLI version run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 087138a1fc..9e5442c1ed 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,6 +107,9 @@ jobs: fetch-depth: 1 persist-credentials: false + - name: Validate Python binding link contract + run: python3 tools/check_python_binding_link_contract.py --ptoas-root . + - name: Install dependencies run: | sudo apt-get update @@ -224,6 +227,7 @@ jobs: - name: Run PTO ISA v0.58 contract gates shell: bash run: | + python3 tools/check_python_binding_link_contract.py --ptoas-root . python3 tools/check_v058_pto_manifest.py --ptoas-root . python3 tools/ptobc/tests/opcode_coverage_check.py \ include/PTO/IR/PTOOps.td \ diff --git a/CMakeLists.txt b/CMakeLists.txt index 36d82bf65f..241731b83e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,11 @@ if(BUILD_TESTING) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_v058_pto_manifest.py --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) + add_test( + NAME python_binding_link_contract_check + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_python_binding_link_contract.py + --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) add_subdirectory(tools/ptobc/tests) endif() diff --git a/lib/Bindings/Python/CMakeLists.txt b/lib/Bindings/Python/CMakeLists.txt index fc43fbb251..e489ef2e4b 100644 --- a/lib/Bindings/Python/CMakeLists.txt +++ b/lib/Bindings/Python/CMakeLists.txt @@ -27,6 +27,15 @@ nanobind_add_module(_pto PTOModule.cpp ) +# CPython extension runtimes intentionally resolve Python ABI symbols from the +# interpreter at module-load time. The project hardening cache enables +# ``-z defs`` globally, so restore the standard extension-module behavior only +# for nanobind's shared runtime on ELF platforms. RELRO/NOW and the remaining +# hardening flags stay enabled. +if(UNIX AND NOT APPLE) + target_link_options(nanobind-mlir PRIVATE "LINKER:-z,undefs") +endif() + target_compile_definitions(_pto PRIVATE MLIR_BINDINGS_PYTHON_DOMAIN=mlir ) diff --git a/tools/check_python_binding_link_contract.py b/tools/check_python_binding_link_contract.py new file mode 100644 index 0000000000..0d132d6d2f --- /dev/null +++ b/tools/check_python_binding_link_contract.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +"""Verify that the shared nanobind runtime keeps the CPython ABI unresolved on ELF.""" + +from __future__ import annotations + +import argparse +import re +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--ptoas-root", type=Path, default=Path.cwd()) + args = parser.parse_args() + + cmake_path = args.ptoas_root / "lib" / "Bindings" / "Python" / "CMakeLists.txt" + source = cmake_path.read_text(encoding="utf-8") + + contract = re.compile( + r"if\s*\(UNIX\s+AND\s+NOT\s+APPLE\).*?" + r"target_link_options\s*\(nanobind-mlir\s+PRIVATE\s+" + r'"LINKER:-z,undefs"\s*\).*?endif\s*\(\)', + re.DOTALL, + ) + if not contract.search(source): + raise SystemExit( + "error: the ELF nanobind-mlir target must override -z defs with " + "LINKER:-z,undefs so CPython resolves its ABI symbols at module load" + ) + + print("OK: Python binding ELF link contract is explicit") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 0ce5fe5e566cd945a4cc1eafcd62621a163ac779 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Wed, 12 Aug 2026 07:39:04 +0800 Subject: [PATCH 11/32] build: add binding contract license header --- tools/check_python_binding_link_contract.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/check_python_binding_link_contract.py b/tools/check_python_binding_link_contract.py index 0d132d6d2f..47dd7c0236 100644 --- a/tools/check_python_binding_link_contract.py +++ b/tools/check_python_binding_link_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Verify that the shared nanobind runtime keeps the CPython ABI unresolved on ELF.""" from __future__ import annotations From 22894a7011867d3755e13826a8898a371f95b2b2 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 00:22:45 +0800 Subject: [PATCH 12/32] release: align PTOAS with PTO ISA 0.58.1 --- .github/scripts/update_pto_isa_pin.py | 27 +- .github/workflows/build_wheel.yml | 4 +- .github/workflows/build_wheel_mac.yml | 4 +- .github/workflows/ci.yml | 15 +- .github/workflows/update_pto_isa_pin.yml | 27 +- CMakeLists.txt | 6 +- README.md | 16 +- docker/Dockerfile | 10 +- docs/PTO_IR_manual.md | 28 +- include/PTO/IR/PTOOps.td | 22 +- lib/PTO/IR/PTO.cpp | 23 +- lib/PTO/Transforms/PTOToEmitC.cpp | 36 +- test/lit/pto/tprefetch_emitc.pto | 17 +- .../tprefetch_verify_negative_byte_count.pto | 11 +- .../tprefetch_verify_oversized_byte_count.pto | 11 +- test/lit/pto/v057_tileop_emitc.pto | 4 +- test/lit/pto/v0581_linx_contract.pto | 36 + test/lit/pto/v058_deleted_ops_rejected.pto | 2 +- test/samples/TPrefetch/tprefetch.py | 4 +- tools/check_v058_pto_manifest.py | 191 +- tools/pto_isa_v0_58_1_lock.json | 47 + .../pto_isa_v0_58_1_operation_contracts.json | 2831 +++++++++++++++++ tools/ptoas/ptoas.cpp | 17 +- tools/ptobc/MAINTENANCE.md | 15 + tools/ptobc/generated/ptobc_opcodes_v0.h | 7 +- tools/ptobc/src/ptobc_decode_print.cpp | 24 +- tools/ptobc/tests/CMakeLists.txt | 8 + tools/ptobc/tests/opcode_coverage_check.py | 14 +- .../tests/ptobc_v0581_contract_encode.sh | 26 + tools/ptobc/update_v0581_schema.py | 75 + tools/test_check_v058_pto_manifest.py | 41 + 31 files changed, 3385 insertions(+), 214 deletions(-) create mode 100644 test/lit/pto/v0581_linx_contract.pto create mode 100644 tools/pto_isa_v0_58_1_lock.json create mode 100644 tools/pto_isa_v0_58_1_operation_contracts.json create mode 100755 tools/ptobc/tests/ptobc_v0581_contract_encode.sh create mode 100755 tools/ptobc/update_v0581_schema.py create mode 100755 tools/test_check_v058_pto_manifest.py diff --git a/.github/scripts/update_pto_isa_pin.py b/.github/scripts/update_pto_isa_pin.py index b247a218e7..49cf712651 100755 --- a/.github/scripts/update_pto_isa_pin.py +++ b/.github/scripts/update_pto_isa_pin.py @@ -3,25 +3,17 @@ import argparse import pathlib import re -import subprocess import sys -DEFAULT_REPO_URL = "https://gitcode.com/cann/pto-isa.git" - - def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( description="Update the pinned pto-isa commit used by CI and Docker." ) - parser.add_argument( - "--repo-url", - default=DEFAULT_REPO_URL, - help="pto-isa git repository URL.", - ) parser.add_argument( "--commit", - help="Commit SHA to pin. If omitted, resolves the current remote HEAD.", + required=True, + help="Explicit reviewed implementation-header commit SHA to pin.", ) parser.add_argument( "--ci-workflow", @@ -41,17 +33,6 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -def resolve_head_commit(repo_url: str) -> str: - out = subprocess.check_output( - ["git", "ls-remote", repo_url, "HEAD"], - text=True, - ).strip() - sha = out.split()[0] if out else "" - if not re.fullmatch(r"[0-9a-f]{40}", sha): - raise RuntimeError(f"failed to resolve HEAD for {repo_url!r}: {out!r}") - return sha - - def read_text(path: pathlib.Path) -> str: return path.read_text(encoding="utf-8") @@ -159,7 +140,9 @@ def verify(ci_path: pathlib.Path, docker_path: pathlib.Path, commit: str) -> Non def main() -> int: args = parse_args() - commit = args.commit or resolve_head_commit(args.repo_url) + commit = args.commit + if not re.fullmatch(r"[0-9a-f]{40}", commit): + raise RuntimeError("--commit must be a full 40-character lowercase SHA") ci_path = pathlib.Path(args.ci_workflow) docker_path = pathlib.Path(args.dockerfile) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index f88351ce43..8b230a6283 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 9fb4f7aa89ee22541bfae288e2085ada266ffd75 + LLVM_COMMIT: b14f16a0cccf1a360a6a64aca8606d7780db3049 jobs: build_wheel: @@ -63,7 +63,7 @@ jobs: run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then EXPECTED_RELEASE_VERSION="$(${PY_PATH}/bin/python .github/scripts/compute_ptoas_version.py --mode release)" - if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.0" ]; then + if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.1" ]; then ${PY_PATH}/bin/python tools/check_v058_pto_manifest.py --ptoas-root . PTOAS_VERSION="${EXPECTED_RELEASE_VERSION}" else diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index bcd83b68f3..f015e3031a 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 9fb4f7aa89ee22541bfae288e2085ada266ffd75 + LLVM_COMMIT: b14f16a0cccf1a360a6a64aca8606d7780db3049 jobs: build_wheel: @@ -66,7 +66,7 @@ jobs: run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then EXPECTED_RELEASE_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode release)" - if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.0" ]; then + if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.1" ]; then python tools/check_v058_pto_manifest.py --ptoas-root . PTOAS_VERSION="${EXPECTED_RELEASE_VERSION}" else diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9e5442c1ed..0612e84329 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ on: type: string default: https://gitcode.com/cann/pto-isa.git pto_isa_commit: - description: "pto-isa ref (commit/tag/branch; empty = repo-pinned weekly commit)" + description: "Reviewed GitCode pto-isa implementation-header commit" type: string # NOTE: Pin a known-good GitCode commit for deterministic runs. default: 662d7f2a916d6bbde3109ce4a16ed5c28f5d900a @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 9fb4f7aa89ee22541bfae288e2085ada266ffd75 + LLVM_COMMIT: b14f16a0cccf1a360a6a64aca8606d7780db3049 LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core @@ -233,7 +233,7 @@ jobs: include/PTO/IR/PTOOps.td \ tools/ptobc/generated/ptobc_opcodes_v0.h ctest --test-dir build --output-on-failure \ - -R 'pto_isa_v0580_contract_check|ptobc_v0580_ops_v0_encode|ptobc_tpartarg_v0_encode' + -R 'pto_isa_v0581_contract_check|ptobc_v0580_ops_v0_encode|ptobc_v0581_contract_encode|ptobc_tpartarg_v0_encode' - name: Run sample tests (py -> pto -> cpp) shell: bash @@ -317,7 +317,7 @@ jobs: SKIP_CASES: ${{ github.event.inputs.skip_cases || '' }} RUN_ONLY_CASES: ${{ github.event.inputs.run_only_cases || '' }} PTO_ISA_REPO: ${{ github.event.inputs.pto_isa_repo || 'https://gitcode.com/cann/pto-isa.git' }} - PTO_ISA_COMMIT: ${{ github.event.inputs.pto_isa_commit || '5dbf1b2f6b8ea934f03e62367c2f540ece21134e' }} + PTO_ISA_COMMIT: ${{ github.event.inputs.pto_isa_commit || '662d7f2a916d6bbde3109ce4a16ed5c28f5d900a' }} REMOTE_HOST: ${{ github.event.inputs.remote_host || '101.245.68.6' }} REMOTE_USER: ${{ github.event.inputs.remote_user || 'zhongxuan' }} REMOTE_PORT: ${{ github.event.inputs.remote_port || '22' }} @@ -403,11 +403,8 @@ jobs: rm -rf "${work}/pto-isa" git clone "${PTO_ISA_REPO}" "${work}/pto-isa" - if [[ -n "${PTO_ISA_COMMIT}" ]]; then - git -C "${work}/pto-isa" checkout -f "${PTO_ISA_COMMIT}" - else - git -C "${work}/pto-isa" checkout -f origin/HEAD || true - fi + test -n "${PTO_ISA_COMMIT}" + git -C "${work}/pto-isa" checkout -f "${PTO_ISA_COMMIT}" # Ship a working tree only; remote should not need outbound network. rm -rf "${work}/pto-isa/.git" diff --git a/.github/workflows/update_pto_isa_pin.yml b/.github/workflows/update_pto_isa_pin.yml index 90a5047062..161a786ba7 100644 --- a/.github/workflows/update_pto_isa_pin.yml +++ b/.github/workflows/update_pto_isa_pin.yml @@ -9,16 +9,20 @@ name: Update pto-isa pin on: - schedule: - - cron: "0 2 * * 1" workflow_dispatch: + inputs: + commit: + description: "Reviewed GitCode pto-isa implementation-header commit" + required: true + type: string permissions: - contents: read + contents: write + pull-requests: write jobs: update-pto-isa-pin: - if: github.repository == 'zhangstevenunity/PTOAS' + if: github.repository == 'LinxISA/ptoas' runs-on: ubuntu-22.04 env: AUTOMATION_TOKEN: ${{ secrets.WORKFLOW_BOT_TOKEN || secrets.RELEASE_BOT_TOKEN || '' }} @@ -50,7 +54,8 @@ jobs: shell: bash run: | set -euo pipefail - sha="$(python .github/scripts/update_pto_isa_pin.py)" + sha="${{ github.event.inputs.commit }}" + python .github/scripts/update_pto_isa_pin.py --commit "${sha}" python .github/scripts/update_pto_isa_pin.py --commit "${sha}" --check echo "pto_isa_commit=${sha}" >> "${GITHUB_OUTPUT}" echo "pto_isa_short=${sha:0:12}" >> "${GITHUB_OUTPUT}" @@ -72,9 +77,17 @@ jobs: git commit -m "chore(ci): bump pto-isa pin to ${{ steps.update.outputs.pto_isa_short }}" echo "created_commit=true" >> "${GITHUB_OUTPUT}" - - name: Push pinned pto-isa update + - name: Push reviewed pin branch and open pull request if: steps.commit.outputs.created_commit == 'true' shell: bash + env: + GH_TOKEN: ${{ github.token }} run: | set -euo pipefail - git push origin HEAD:${{ github.event.repository.default_branch }} + branch="automation/pto-isa-${{ steps.update.outputs.pto_isa_short }}" + git push --force-with-lease origin "HEAD:${branch}" + gh pr create \ + --base "${{ github.event.repository.default_branch }}" \ + --head "${branch}" \ + --title "chore(ci): pin reviewed pto-isa implementation ${{ steps.update.outputs.pto_isa_short }}" \ + --body "Pins the separately reviewed GitCode implementation-header dependency. This is not the PTO-SPEC source commit." diff --git a/CMakeLists.txt b/CMakeLists.txt index 241731b83e..03c5bfb988 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,10 +135,14 @@ if(BUILD_TESTING) enable_testing() find_package(Python3 COMPONENTS Interpreter REQUIRED) add_test( - NAME pto_isa_v0580_contract_check + NAME pto_isa_v0581_contract_check COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_v058_pto_manifest.py --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) + add_test( + NAME pto_isa_v0581_contract_check_negative + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_check_v058_pto_manifest.py) add_test( NAME python_binding_link_contract_check COMMAND ${Python3_EXECUTABLE} diff --git a/README.md b/README.md index 730f52ee1d..e8865e954f 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ ## 1. 项目简介 (Introduction) **ptoas** (`ptoas`) 是一个基于 LinxISA 受控 **LLVM/MLIR** -(release `linxisa-v0.58.0`,commit -`9fb4f7aa89ee22541bfae288e2085ada266ffd75`)构建的专用编译器工具链, +(ISA release `linxisa-v0.58.1`,LLVM commit +`b14f16a0cccf1a360a6a64aca8606d7780db3049`)构建的专用编译器工具链, 专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 作为连接上层 AI 框架与底层各类NPU/GPGPU/CPU硬件,`ptoas` 采用 **Out-of-Tree** 架构构建,提供了完整的 C++ 与 Python 接口,主要职责包括: @@ -41,8 +41,8 @@ PTOAS/ ## 3. 构建指南 (Build Instructions) ⚠️ **重要提示**:本项目严格依赖 LinxISA LLVM -`9fb4f7aa89ee22541bfae288e2085ada266ffd75`。该 SHA 对应 -`linxisa-v0.58.0`,不要替换为同名上游 LLVM tag。 +`b14f16a0cccf1a360a6a64aca8606d7780db3049`。`linxisa-v0.58.1` +是 ISA release tag,不是 PTOAS 产品版本;不要替换为同名上游 LLVM tag。 ### 3.0 环境变量配置 (Configuration) @@ -92,7 +92,7 @@ python3 -m pip install nanobind numpy cd $WORKSPACE_DIR git clone https://github.com/LinxISA/llvm-project.git cd $LLVM_SOURCE_DIR -git checkout --detach 9fb4f7aa89ee22541bfae288e2085ada266ffd75 +git checkout --detach b14f16a0cccf1a360a6a64aca8606d7780db3049 # 2. 配置 CMake (构建动态库并启用 Python 绑定) cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ @@ -215,12 +215,14 @@ ptoas test/lit/pto/v058_linx_target.pto --pto-arch=linx -o outputfile.cpp # 指定构建 Level(level3 会禁用 PlanMemory/InsertSync) ptoas test/lit/pto/empty_func.pto --pto-level=level3 -o outputfile.cpp -# 查看当前 ptoas release 版本号 +# 查看 PTOAS 产品版本及其 PTO ISA contract 版本 ptoas --version ``` -`linx` target 对齐受管的 `Linx-TileOP-API`:生成代码包含 +`ptoas --version` 中的 `0.41` 是 PTOAS 产品版本;括号中的 `PTO ISA +0.58.1` 是独立的 ISA contract 版本。`linx` target 对齐受管的 +`Linx-TileOP-API`:生成代码包含 `jcore/template_asm.hpp`,只接受 v0.58 公共 PTO 操作目录,并在 lowering 前拒绝仅属于 A3/A5 方言面的操作。 diff --git a/docker/Dockerfile b/docker/Dockerfile index 6bcf9f7889..e310763e5c 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -10,7 +10,8 @@ ARG ARCH # NOTE: change $PY_VER for different Python versions (3.8 - 3.14 available) ARG PY_VER=cp311-cp311 -ARG LLVM_TAG=llvmorg-19.1.7 +ARG LLVM_REPOSITORY=https://github.com/LinxISA/llvm-project.git +ARG LLVM_COMMIT=b14f16a0cccf1a360a6a64aca8606d7780db3049 ## -- usually no need to change below -- @@ -33,11 +34,16 @@ ENV PTO_INSTALL_DIR=$PTO_SOURCE_DIR/install # build LLVM WORKDIR $WORKSPACE_DIR -RUN git clone --depth 1 --branch ${LLVM_TAG} https://github.com/llvm/llvm-project.git +RUN git clone --no-checkout ${LLVM_REPOSITORY} ${LLVM_SOURCE_DIR} \ + && git -C ${LLVM_SOURCE_DIR} fetch --depth 1 origin ${LLVM_COMMIT} \ + && git -C ${LLVM_SOURCE_DIR} checkout --detach ${LLVM_COMMIT} \ + && test "$(git -C ${LLVM_SOURCE_DIR} rev-parse HEAD)" = "${LLVM_COMMIT}" WORKDIR $LLVM_SOURCE_DIR RUN cmake -C /tmp/LinuxHardeningCache.cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ -DLLVM_ENABLE_PROJECTS="mlir;clang" \ + -DMLIR_ENABLE_LINXISA_PTO=OFF \ + -DLLVM_INCLUDE_TESTS=OFF \ -DBUILD_SHARED_LIBS=ON \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ -DPython3_EXECUTABLE=${PY_PATH}/bin/python \ diff --git a/docs/PTO_IR_manual.md b/docs/PTO_IR_manual.md index 2db9461552..2abb049813 100644 --- a/docs/PTO_IR_manual.md +++ b/docs/PTO_IR_manual.md @@ -793,17 +793,17 @@ pto.tload ins(%pv : !pto.partition_tensor_view<16x16xf16>) --- -##### `pto.tprefetch` - Prefetch a Global Byte Range +##### `pto.tprefetch` - Prefetch a Global Tile Region -**Summary:** Probes and prefetches a global byte range with TLOAD-equivalent +**Summary:** Probes and prefetches a rectangular global tile region with TLOAD-equivalent translation, permission, fault, restart, coherence, and ordering behavior. PTO -ISA 0.57.1 deliberately makes this operation destination-free: it changes +ISA 0.58.1 makes this operation destination-free: it changes target cache or data-movement state without publishing a tile result. **Semantics:** ``` -TPREFETCH(address, byte_count) +TPREFETCH(address, row_stride, valid_cols, valid_rows, physical_cols) ``` The detailed cache placement is target-defined, but access faults are @@ -815,7 +815,10 @@ intrinsic wrappers, `TPREFETCH` does not add implicit wait-event synchronization | Name | Type | Description | |------|------|-------------| | `address` | `i64` | Base address of the global byte range | -| `byte_count` | `index` | Number of bytes to prefetch | +| `row_stride` | `index` | Row stride in elements | +| `valid_cols` | `index` | Number of valid columns | +| `valid_rows` | `index` | Number of valid rows | +| `physical_cols` | `index` | Physical row width in elements | **Results:** None. The operation has no destination tile and publishes no result queue value. @@ -823,24 +826,25 @@ queue value. **Constraints & Verification:** - `address` must have exactly type `i64`. -- `byte_count` is an `index` operand and denotes bytes, not elements. -- `byte_count` must be in the inclusive range `0..262144`. PTOAS rejects an - out-of-range compile-time constant; a dynamic value remains legal in the IR - and must satisfy the same range when executed. +- Shape and stride operands have `index` type and must be nonnegative. - A partition view or tile destination is not a compatibility spelling and is rejected by the PTOAS parser/verifier. **Hardware Mapping:** - Executes on the **DMA pipeline** (`PIPE_MTE2`). -- EmitC lowers exactly to `TPREFETCH(address, byte_count)`. +- EmitC lowers exactly to + `TPREFETCH(address, row_stride, valid_cols, valid_rows, physical_cols)`. **Basic Example:** ```mlir %address = arith.constant 4096 : i64 -%bytes = arith.constant 512 : index -pto.tprefetch ins(%address, %bytes : i64, index) +%stride = arith.constant 64 : index +%cols = arith.constant 32 : index +%rows = arith.constant 16 : index +%physical_cols = arith.constant 64 : index +pto.tprefetch ins(%address, %stride, %cols, %rows, %physical_cols : i64, index, index, index, index) ``` --- diff --git a/include/PTO/IR/PTOOps.td b/include/PTO/IR/PTOOps.td index c3aa727b0b..2033b647d1 100644 --- a/include/PTO/IR/PTOOps.td +++ b/include/PTO/IR/PTOOps.td @@ -522,16 +522,18 @@ def TPrefetchOp : PTO_TOp<"tprefetch", [ OpPipeInterface, DeclareOpInterfaceMethods ]> { - let summary = "Prefetch a byte range from a global address."; + let summary = "Prefetch a rectangular tile region from a global address."; let description = [{ - PTO ISA 0.57.1 models TPREFETCH as a destination-free TMA operation over - a base address and byte count. Unlike most PTO intrinsics, TPREFETCH does - not carry implicit TSYNC(wait_events...) semantics in PTO-ISA. + PTO ISA 0.58.1 models TPREFETCH as a destination-free TLSU operation over + a base address, element row stride, valid shape, and physical row width. }]; let arguments = (ins I64:$address, - Index:$byte_count + Index:$row_stride, + Index:$valid_cols, + Index:$valid_rows, + Index:$physical_cols ); let results = (outs); @@ -539,7 +541,9 @@ def TPrefetchOp : PTO_TOp<"tprefetch", [ let hasVerifier = 1; let assemblyFormat = [{ - `ins` `(` $address `,` $byte_count `:` type($address) `,` type($byte_count) `)` + `ins` `(` $address `,` $row_stride `,` $valid_cols `,` $valid_rows `,` $physical_cols + `:` type($address) `,` type($row_stride) `,` type($valid_cols) `,` + type($valid_rows) `,` type($physical_cols) `)` attr-dict }]; @@ -4201,17 +4205,19 @@ def TImg2ColOp : PTO_TOp<"timg2col", [ OpPipeInterface, DeclareOpInterfaceMethods ]> { - let summary = "TIMG2COL(src, dst) image-to-column tile transform"; + let summary = "TIMG2COL(src, posM, posK, dst) image-to-column tile transform"; let arguments = (ins PTODpsType:$src, + Index:$posM, + Index:$posK, PTODpsType:$dst ); let results = (outs); let assemblyFormat = [{ - `ins` `(` $src `:` qualified(type($src)) `)` + `ins` `(` $src `,` $posM `,` $posK `:` qualified(type($src)) `,` type($posM) `,` type($posK) `)` `outs` `(` $dst `:` qualified(type($dst) ) `)` attr-dict }]; diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index 6537221f99..feb631835d 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -39,6 +39,7 @@ #include "llvm/Support/ErrorHandling.h" #include +#include #include #include #include @@ -2327,10 +2328,14 @@ LogicalResult TLoadOp::verify() { LogicalResult TPrefetchOp::verify() { if (!getAddress().getType().isInteger(64)) return emitOpError("expects address to have type i64"); - if (auto byteCount = getConstantIntegerValue(getByteCount()); - byteCount && (*byteCount < 0 || *byteCount > 262144)) - return emitOpError( - "expects a static byte_count in the inclusive range 0..262144"); + constexpr std::array operandNames = { + "row_stride", "valid_cols", "valid_rows", "physical_cols"}; + SmallVector operands = {getRowStride(), getValidCols(), + getValidRows(), getPhysicalCols()}; + for (auto [name, value] : llvm::zip_equal(operandNames, operands)) { + if (auto constant = getConstantIntegerValue(value); constant && *constant < 0) + return emitOpError() << "expects " << name << " to be nonnegative"; + } return success(); } @@ -10060,12 +10065,15 @@ void TLoadOp::getEffects(SmallVectorImpl> &effects) { - // Model the architectural cache-state mutation, not just reads of the two + // Model the architectural cache-state mutation, not just reads of the five // scalar operands. The conservative unbound write keeps generic DCE from // erasing a destination-free prefetch hint. effects.emplace_back(MemoryEffects::Write::get()); addEffect(effects, &getAddressMutable(), MemoryEffects::Read::get()); - addEffect(effects, &getByteCountMutable(), MemoryEffects::Read::get()); + addEffect(effects, &getRowStrideMutable(), MemoryEffects::Read::get()); + addEffect(effects, &getValidColsMutable(), MemoryEffects::Read::get()); + addEffect(effects, &getValidRowsMutable(), MemoryEffects::Read::get()); + addEffect(effects, &getPhysicalColsMutable(), MemoryEffects::Read::get()); } // === TAbsOp === @@ -10332,10 +10340,11 @@ void TExtractOp::getEffects( PTO_ADD_WRITE(getDstMutable()); } -// TINSERT: Read(src) -> Write(dst) +// TINSERT: Read(old dst), Read(src) -> Write(dst) void TInsertOp::getEffects( SmallVectorImpl> &effects) { PTO_ADD_READ(getSrcMutable()); + PTO_ADD_READ(getDstMutable()); PTO_ADD_WRITE(getDstMutable()); } diff --git a/lib/PTO/Transforms/PTOToEmitC.cpp b/lib/PTO/Transforms/PTOToEmitC.cpp index 2190c77155..9287de2804 100644 --- a/lib/PTO/Transforms/PTOToEmitC.cpp +++ b/lib/PTO/Transforms/PTOToEmitC.cpp @@ -20,10 +20,6 @@ #include "PTO/IR/PTOSyncUtils.h" #include "PTO/Transforms/Passes.h" -#include "mlir/Analysis/DataFlow/DeadCodeAnalysis.h" -#include "mlir/Analysis/DataFlow/IntegerRangeAnalysis.h" -#include "mlir/Analysis/DataFlowFramework.h" - #include "mlir/Dialect/Affine/IR/AffineOps.h" #include "mlir/Dialect/Arith/IR/Arith.h" #include "mlir/Dialect/EmitC/IR/EmitC.h" @@ -3735,7 +3731,8 @@ static Value maybeWrapGlobalMemrefAsGlobalTensor( static Value castToGMBytePointer(ConversionPatternRewriter &rewriter, Location loc, Value value) { auto *ctx = rewriter.getContext(); - auto targetTy = emitc::OpaqueType::get(ctx, "__gm__ uint8_t*"); + auto targetTy = emitc::PointerType::get( + emitc::OpaqueType::get(ctx, "__gm__ uint8_t")); if (value.getType() == targetTy) return value; @@ -4259,11 +4256,15 @@ struct PTOTPrefetchToTPREFETCH : public OpConversionPattern { LogicalResult matchAndRewrite(pto::TPrefetchOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Value address = peelUnrealized(adaptor.getAddress()); - Value byteCount = peelUnrealized(adaptor.getByteCount()); + Value rowStride = peelUnrealized(adaptor.getRowStride()); + Value validCols = peelUnrealized(adaptor.getValidCols()); + Value validRows = peelUnrealized(adaptor.getValidRows()); + Value physicalCols = peelUnrealized(adaptor.getPhysicalCols()); rewriter.create( op.getLoc(), TypeRange{}, "TPREFETCH", - ArrayAttr{}, ArrayAttr{}, ValueRange{address, byteCount}); + ArrayAttr{}, ArrayAttr{}, + ValueRange{address, rowStride, validCols, validRows, physicalCols}); rewriter.eraseOp(op); return success(); } @@ -5825,7 +5826,8 @@ struct PTOInitializeL2LPipeToEmitC auto emitPipeTy = cast(getTypeConverter()->convertType(op.getPipe().getType())); - auto gmPtrTy = emitc::OpaqueType::get(ctx, "__gm__ void *"); + auto gmPtrTy = emitc::PointerType::get( + emitc::OpaqueType::get(ctx, "__gm__ void")); Value nullGm = makeEmitCOpaqueConstant(rewriter, op.getLoc(), gmPtrTy, "nullptr"); auto i32Ty = emitc::OpaqueType::get(ctx, "int32_t"); @@ -7934,7 +7936,7 @@ struct PTOExtractFPToEmitC : public OpConversionPattern { } }; //===----------------------------------------------------------------------===// -// pto.tinsert lowering -> TINSERT(dst, src, indexRow, indexCol) +// pto.tinsert lowering -> TINSERT(dst, dst, src, indexRow, indexCol) // Keep lowering arch-agnostic and let PTO-ISA infer proper A5 path. //===----------------------------------------------------------------------===// @@ -7953,7 +7955,7 @@ struct PTOInsertToEmitC : public OpConversionPattern { rewriter.create( loc, TypeRange{}, "TINSERT", /*args=*/ArrayAttr{}, /*templateArgs=*/ArrayAttr{}, - /*operands=*/ValueRange{dst, src, r0, c0}); + /*operands=*/ValueRange{dst, dst, src, r0, c0}); rewriter.eraseOp(op); return success(); @@ -8200,12 +8202,14 @@ struct PTOImg2ColToEmitC : public OpConversionPattern { LogicalResult matchAndRewrite(pto::TImg2ColOp op, OpAdaptor adaptor, ConversionPatternRewriter &rewriter) const override { Value src = peelUnrealized(adaptor.getSrc()); + Value posM = peelUnrealized(adaptor.getPosM()); + Value posK = peelUnrealized(adaptor.getPosK()); Value dst = peelUnrealized(adaptor.getDst()); rewriter.create( op.getLoc(), TypeRange{}, "TIMG2COL", /*args=*/ArrayAttr{}, /*templateArgs=*/ArrayAttr{}, - /*operands=*/ValueRange{dst, src}); + /*operands=*/ValueRange{dst, src, posM, posK}); rewriter.eraseOp(op); return success(); @@ -11725,9 +11729,7 @@ struct CFSwitchToCondBr : public OpRewritePattern { static void populatePTOToEmitCPatterns(RewritePatternSet &patterns, TypeConverter &typeConverter, MLIRContext *ctx, - DataFlowSolver &solver, PTOArch targetArch) { - (void)solver; patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); patterns.add(typeConverter, ctx); @@ -12269,14 +12271,8 @@ static AICORE inline void ptoas_auto_sync_tail( target.addLegalDialect(); target.addLegalOp(); - auto solver = std::make_unique(); - solver->load(); - solver->load(); - if (failed(solver->initializeAndRun(getOperation()))) - return signalPassFailure(); - RewritePatternSet patterns(ctx); - populatePTOToEmitCPatterns(patterns, typeConverter, ctx, *solver, targetArch); + populatePTOToEmitCPatterns(patterns, typeConverter, ctx, targetArch); // 4. 执行转换 if (failed(applyPartialConversion(mop, target, std::move(patterns)))) { diff --git a/test/lit/pto/tprefetch_emitc.pto b/test/lit/pto/tprefetch_emitc.pto index 70f307164d..583c623be0 100644 --- a/test/lit/pto/tprefetch_emitc.pto +++ b/test/lit/pto/tprefetch_emitc.pto @@ -1,17 +1,16 @@ // RUN: ptoas %s | FileCheck %s --check-prefix=A3 module attributes {pto.target_arch = "a3"} { - func.func @tprefetch_emitc(%dynamic_bytes: index) { + func.func @tprefetch_emitc(%dynamic_rows: index) { %addr = arith.constant 4096 : i64 - %bytes = arith.constant 512 : index - %max_bytes = arith.constant 262144 : index - pto.tprefetch ins(%addr, %bytes : i64, index) - pto.tprefetch ins(%addr, %max_bytes : i64, index) - pto.tprefetch ins(%addr, %dynamic_bytes : i64, index) + %stride = arith.constant 64 : index + %cols = arith.constant 32 : index + %rows = arith.constant 16 : index + %physical_cols = arith.constant 64 : index + pto.tprefetch ins(%addr, %stride, %cols, %rows, %physical_cols : i64, index, index, index, index) + pto.tprefetch ins(%addr, %stride, %cols, %dynamic_rows, %physical_cols : i64, index, index, index, index) return } } -// A3: TPREFETCH( -// A3: TPREFETCH( -// A3: TPREFETCH( +// A3-COUNT-2: TPREFETCH({{[^,]+}}, {{[^,]+}}, {{[^,]+}}, {{[^,]+}}, {{[^)]+}}) diff --git a/test/lit/pto/tprefetch_verify_negative_byte_count.pto b/test/lit/pto/tprefetch_verify_negative_byte_count.pto index 926ff08ae0..9a892b6973 100644 --- a/test/lit/pto/tprefetch_verify_negative_byte_count.pto +++ b/test/lit/pto/tprefetch_verify_negative_byte_count.pto @@ -1,12 +1,15 @@ // RUN: not ptoas --pto-arch=a5 %s 2>&1 | FileCheck %s module attributes {pto.target_arch = "a5"} { - func.func @tprefetch_rejects_negative_byte_count() { + func.func @tprefetch_rejects_negative_row_stride() { %addr = arith.constant 4096 : i64 - %bytes = arith.constant -1 : index - pto.tprefetch ins(%addr, %bytes : i64, index) + %row_stride = arith.constant -1 : index + %valid_cols = arith.constant 32 : index + %valid_rows = arith.constant 16 : index + %physical_cols = arith.constant 64 : index + pto.tprefetch ins(%addr, %row_stride, %valid_cols, %valid_rows, %physical_cols : i64, index, index, index, index) return } } -// CHECK: error: 'pto.tprefetch' op expects a static byte_count in the inclusive range 0..262144 +// CHECK: error: 'pto.tprefetch' op expects row_stride to be nonnegative diff --git a/test/lit/pto/tprefetch_verify_oversized_byte_count.pto b/test/lit/pto/tprefetch_verify_oversized_byte_count.pto index 4026bb10ce..9e718f3b4b 100644 --- a/test/lit/pto/tprefetch_verify_oversized_byte_count.pto +++ b/test/lit/pto/tprefetch_verify_oversized_byte_count.pto @@ -1,12 +1,15 @@ // RUN: not ptoas --pto-arch=a5 %s 2>&1 | FileCheck %s module attributes {pto.target_arch = "a5"} { - func.func @tprefetch_rejects_oversized_byte_count() { + func.func @tprefetch_rejects_negative_physical_cols() { %addr = arith.constant 4096 : i64 - %bytes = arith.constant 262145 : index - pto.tprefetch ins(%addr, %bytes : i64, index) + %row_stride = arith.constant 64 : index + %valid_cols = arith.constant 32 : index + %valid_rows = arith.constant 16 : index + %physical_cols = arith.constant -1 : index + pto.tprefetch ins(%addr, %row_stride, %valid_cols, %valid_rows, %physical_cols : i64, index, index, index, index) return } } -// CHECK: error: 'pto.tprefetch' op expects a static byte_count in the inclusive range 0..262144 +// CHECK: error: 'pto.tprefetch' op expects physical_cols to be nonnegative diff --git a/test/lit/pto/v057_tileop_emitc.pto b/test/lit/pto/v057_tileop_emitc.pto index bd6311ebae..18fabcd50f 100644 --- a/test/lit/pto/v057_tileop_emitc.pto +++ b/test/lit/pto/v057_tileop_emitc.pto @@ -18,8 +18,10 @@ module attributes {"pto.device-spec" = "Ascend910B3"} { %src1 = pto.alloc_tile : !pto.tile_buf %dst0 = pto.alloc_tile : !pto.tile_buf %dst1 = pto.alloc_tile : !pto.tile_buf + %posM = arith.constant 0 : index + %posK = arith.constant 0 : index - pto.timg2col ins(%src0 : !pto.tile_buf) + pto.timg2col ins(%src0, %posM, %posK : !pto.tile_buf, index, index) outs(%dst0 : !pto.tile_buf) pto.tdeinterleave ins(%dst0 : !pto.tile_buf) diff --git a/test/lit/pto/v0581_linx_contract.pto b/test/lit/pto/v0581_linx_contract.pto new file mode 100644 index 0000000000..3e450af80f --- /dev/null +++ b/test/lit/pto/v0581_linx_contract.pto @@ -0,0 +1,36 @@ +// Copyright (c) 2026 Huawei Technologies Co., Ltd. +// This program is free software, you can redistribute it and/or modify it under the terms and conditions of +// CANN Open Software License Agreement Version 2.0 (the "License"). +// Please refer to the License for details. You may not use this file except in compliance with the License. +// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +// See LICENSE in the root of the software repository for the full text of the License. + +// RUN: ptoas --pto-arch=linx %s | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @v0581_contract() attributes {pto.kernel_kind = #pto.kernel_kind} { + %address = arith.constant 4096 : i64 + %row_stride = arith.constant 64 : index + %valid_cols = arith.constant 32 : index + %valid_rows = arith.constant 16 : index + %physical_cols = arith.constant 64 : index + %pos_m = arith.constant 3 : index + %pos_k = arith.constant 5 : index + %row = arith.constant 0 : index + %col = arith.constant 0 : index + %src = pto.alloc_tile : !pto.tile_buf + %dst = pto.alloc_tile : !pto.tile_buf + + pto.tprefetch ins(%address, %row_stride, %valid_cols, %valid_rows, %physical_cols : i64, index, index, index, index) + pto.timg2col ins(%src, %pos_m, %pos_k : !pto.tile_buf, index, index) + outs(%dst : !pto.tile_buf) + pto.tinsert ins(%src, %row, %col : !pto.tile_buf, index, index) + outs(%dst : !pto.tile_buf) + return + } +} + +// CHECK: TPREFETCH({{[^,]+}}, {{[^,]+}}, {{[^,]+}}, {{[^,]+}}, {{[^)]+}}) +// CHECK: TIMG2COL({{[^,]+}}, {{[^,]+}}, {{[^,]+}}, {{[^)]+}}) +// CHECK: TINSERT([[DST:[^,]+]], [[DST]], {{[^,]+}}, {{[^,]+}}, {{[^)]+}}) diff --git a/test/lit/pto/v058_deleted_ops_rejected.pto b/test/lit/pto/v058_deleted_ops_rejected.pto index 47ef24a22d..0c90edccd2 100644 --- a/test/lit/pto/v058_deleted_ops_rejected.pto +++ b/test/lit/pto/v058_deleted_ops_rejected.pto @@ -8,4 +8,4 @@ module attributes {pto.target_arch = "linx"} { } } -// CHECK: error: 'pto.acccvt' op is dialect-only and not part of the active Linx PTO ISA v0.58.0 target +// CHECK: error: 'pto.acccvt' op is dialect-only and not part of the active Linx PTO ISA v0.58.1 target diff --git a/test/samples/TPrefetch/tprefetch.py b/test/samples/TPrefetch/tprefetch.py index 7b132806f2..8d7d9a0b11 100644 --- a/test/samples/TPrefetch/tprefetch.py +++ b/test/samples/TPrefetch/tprefetch.py @@ -18,13 +18,13 @@ def build(): module = Module.create() i64 = IntegerType.get_signless(64, ctx) idx = IndexType.get(ctx) - fn_ty = func.FunctionType.get([i64, idx], []) + fn_ty = func.FunctionType.get([i64, idx, idx, idx, idx], []) with InsertionPoint(module.body): fn = func.FuncOp("tprefetch_kernel", fn_ty) entry = fn.add_entry_block() with InsertionPoint(entry): - pto.TPrefetchOp(entry.arguments[0], entry.arguments[1]) + pto.TPrefetchOp(*entry.arguments) func.ReturnOp([]) module.operation.verify() diff --git a/tools/check_v058_pto_manifest.py b/tools/check_v058_pto_manifest.py index 3fb5d726ed..f621c8ce9f 100644 --- a/tools/check_v058_pto_manifest.py +++ b/tools/check_v058_pto_manifest.py @@ -7,12 +7,12 @@ # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. # See LICENSE in the root of the software repository for the full text of the License. -"""Check PTOAS PTO-op contracts against the LinxISA v0.58.0 PTO manifest. +"""Check PTOAS PTO-op contracts against the LinxISA v0.58.1 PTO manifest. PTOAS is an MLIR PTO dialect-to-EmitC compiler, not a Linx scalar assembler. This check validates all 109 public operations' exact Linx operand roles/arity, -the matching PTOAS ODS argument surface, the explicit v0.57.1-to-v0.58.0 -contract delta, and the bounded dialect-only surface. +the matching PTOAS ODS argument surface, exact operation engines and roles, +and the bounded dialect-only surface. """ from __future__ import annotations @@ -26,23 +26,30 @@ EXPECTED_LOCK = { - "release": "0.58.0", - "encoding_abi": "pto-isa-0.58.0-mode-function-v1", - "encoding_projection_sha256": "0cad2272ada8f53fc8354e22568099fe8d6bd4b7832c837260cd370b0fc76ffa", - "content_sha256": "355e96045cd3f159c367c75d32b1b2d0a8438cd8df61def2b5d4cd650d103873", - "release_manifest_sha256": "9f9a5c81cb78b5409e88eb8007bb89d145e298377a458f15a6dbb0ee9ff0ceee", - "source_commit": "1c2cb0dcafdbc151357c83e89e7d9460b5d9f401", + "release": "0.58.1", + "encoding_abi": "pto-isa-0.58.1-mode-function-v1", + "encoding_projection_sha256": "89b872d6eaf0252200bc9349d49b9346e2a69d894cdcc2dcd0fd71911c1e0b8c", + "content_sha256": "693e8c0734b48598ac35ffe7fe6f2a01037788fba30ebe026895808d23139f2c", + "release_manifest_sha256": "acea87af67301173e6d1c6e04014a8dc6e2f658cd2992ed658ab2589cddc7841", + "source_commit": "c381465b2b8e457e162a4246ee58bb9a2c5b49fd", + "source_tree": "463a19db3d6ba70022f18bdbca0d4b2c6ed586e4", "hardware_profile_path": "spec/hardware-conformance-profile.json", - "hardware_profile_id": "pto-hardware-numeric-0.58.0-ieee-v1", - "hardware_profile_sha256": "c4076cf8ac6ebd0c6db8a070b7c290b9928ed7f98c089538ee53ed4644e1531a", - "numeric_vectors_path": "spec/evidence/pto-isa-0580-hardware-numeric-vectors.json", - "numeric_vectors_sha256": "0ad9405b5b7da3803fcf3f803c66ea66918d4363e4342356b1f818804d7f30e8", + "hardware_profile_id": "pto-hardware-numeric-0.58.1-ieee-v1", + "hardware_profile_sha256": "170deadacb174c933c287231fb67da1046d7989f84b6852bf353d68a495d1755", + "numeric_vectors_path": "spec/evidence/pto-isa-0581-hardware-numeric-vectors.json", + "numeric_vectors_sha256": "59c96cc2f45f8e8f3eebb8230338b21ec3a77a99e8fb5e1c7c7b391819a6aa81", "command_forms_path": "spec/catalog/command-forms.json", - "command_forms_sha256": "aaa16ddd046b7c6ee06aedd6444c90da2541c6f3204ef8d12dba34815e96f15b", - "command_forms_count": 99, + "command_forms_sha256": "300a3a57a8728e6c4770da6fff0202b372ec2830edb8dc978dc141d1c26424d0", + "command_forms_count": 74, + "scalar_forms_path": "spec/catalog/scalar-forms.json", + "scalar_forms_sha256": "9f3841d568ffa73fcb43bf4fd365d3c4dba42d27acffa7e273e0f403c0f0c602", + "scalar_forms_count": 474, "tile_operations_path": "spec/catalog/tile-operations.json", - "tile_operations_sha256": "aa1fa0a5ba07c7f015875025cc93c42f49b8d4313c55ce2cd72eaaa51bdc7d56", + "tile_operations_sha256": "f163dea8be281fd67173713d373b60f95a9c3c4e558adcdf8034cc213507a1a3", "tile_operation_count": 109, + "extension_encoding_reservations_path": "spec/catalog/extension-encoding-reservations.json", + "extension_encoding_reservations_sha256": "bdb82b839b98984779d9a1394f6b308f141052ef0b520e5bedb8e87dadd883d4", + "extension_encoding_reservations_count": 32, "release_manifest_path": "spec/release-manifest.json", "source_repository": "https://github.com/PTO-ISA/pto-spec.git", } @@ -81,7 +88,7 @@ def sha256(path: Path) -> str: def load_lock(ptoas_root: Path) -> dict: - lock_path = ptoas_root / "tools/pto_isa_v0_58_0_lock.json" + lock_path = ptoas_root / "tools/pto_isa_v0_58_1_lock.json" lock = json.loads(lock_path.read_text()) errors = [] for key in ("release", "encoding_abi", "encoding_projection_sha256", "content_sha256"): @@ -97,6 +104,7 @@ def load_lock(ptoas_root: Path) -> dict: source = lock.get("source", {}) for key, expected_key in ( ("commit", "source_commit"), + ("tree", "source_tree"), ("repository", "source_repository"), ): if source.get(key) != EXPECTED_LOCK[expected_key]: @@ -122,17 +130,29 @@ def load_lock(ptoas_root: Path) -> dict: "command_forms", ("command_forms_path", "command_forms_sha256", "command_forms_count"), ), + ( + "scalar_forms", + ("scalar_forms_path", "scalar_forms_sha256", "scalar_forms_count"), + ), ( "tile_operations", ("tile_operations_path", "tile_operations_sha256", "tile_operation_count"), ), + ( + "extension_encoding_reservations", + ( + "extension_encoding_reservations_path", + "extension_encoding_reservations_sha256", + "extension_encoding_reservations_count", + ), + ), ): entry = catalogs.get(catalog, {}) for key, expected_key in zip(("path", "sha256", "count"), expected_keys): if entry.get(key) != EXPECTED_LOCK[expected_key]: errors.append(f"catalogs.{catalog}.{key} mismatch") if errors: - raise SystemExit("unexpected PTO ISA 0.58.0 lock:\n " + "\n ".join(errors)) + raise SystemExit("unexpected PTO ISA 0.58.1 lock:\n " + "\n ".join(errors)) return lock @@ -150,10 +170,19 @@ def validate_source_tree(source_root: Path, lock: dict) -> None: raise SystemExit( f"PTO ISA source commit mismatch: expected {lock['source']['commit']}, got {head}" ) + tree = subprocess.run( + ["git", "-C", str(source_root), "rev-parse", "HEAD^{tree}"], + check=True, + capture_output=True, + text=True, + ).stdout.strip() + if tree != lock["source"]["tree"]: + raise SystemExit( + f"PTO ISA source tree mismatch: expected {lock['source']['tree']}, got {tree}" + ) pinned_files = ( - lock["catalogs"]["command_forms"], - lock["catalogs"]["tile_operations"], + *lock["catalogs"].values(), lock["release_manifest"], lock["hardware_conformance_profile"], lock["numeric_conformance_vectors"], @@ -211,10 +240,10 @@ def load_manifest(linx_root: Path) -> dict[str, dict]: manifest_path = linx_root / "isa/v0.58/state/pto_ops.json" manifest = json.loads(manifest_path.read_text()) if manifest["profile"] != "v0.58" or manifest["operation_count"] != 109: - raise SystemExit(f"unexpected v0.58.0 manifest header in {manifest_path}") + raise SystemExit(f"unexpected v0.58.1 manifest header in {manifest_path}") source_lock = manifest.get("source_lock") if source_lock != "isa/v0.58/pto-spec.lock.json": - raise SystemExit(f"unexpected v0.58.0 source_lock in {manifest_path}: {source_lock}") + raise SystemExit(f"unexpected v0.58.1 source_lock in {manifest_path}: {source_lock}") operations = manifest["operations"] names = [entry["name"] for entry in operations] if len(names) != len(set(names)): @@ -222,39 +251,58 @@ def load_manifest(linx_root: Path) -> dict[str, dict]: return {entry["name"]: entry for entry in operations} +def validate_linx_identity(linx_root: Path, lock: dict) -> None: + root_lock_path = linx_root / "isa/v0.58/pto-spec.lock.json" + root_lock = json.loads(root_lock_path.read_text()) + if root_lock != lock: + local_text = json.dumps(lock, sort_keys=True, indent=2) + root_text = json.dumps(root_lock, sort_keys=True, indent=2) + raise SystemExit( + "PTOAS/root PTO ISA lock mismatch:\n" + f"--- PTOAS lock\n{local_text}\n--- LinxISA lock\n{root_text}" + ) + + release_path = linx_root / "isa/v0.58/release_manifest.json" + release = json.loads(release_path.read_text()) + if release.get("profile") != "v0.58" or release.get("version") != lock["release"]: + raise SystemExit( + f"LinxISA release manifest mismatch: expected v0.58/{lock['release']}, " + f"got {release.get('profile')}/{release.get('version')}" + ) + cardinality = release.get("cardinality", {}) + expected_counts = { + "command_forms": lock["catalogs"]["command_forms"]["count"], + "scalar_forms": lock["catalogs"]["scalar_forms"]["count"], + "tile_operations": lock["catalogs"]["tile_operations"]["count"], + "extension_encoding_reservations": lock["catalogs"]["extension_encoding_reservations"]["count"], + } + for name, expected in expected_counts.items(): + if cardinality.get(name) != expected: + raise SystemExit( + f"LinxISA release manifest {name} count mismatch: " + f"expected {expected}, got {cardinality.get(name)}" + ) + + def load_expected_contracts(ptoas_root: Path) -> tuple[dict[str, dict], dict[str, dict]]: - delta_path = ptoas_root / "tools/pto_isa_v0_58_0_operation_contracts.json" - delta = json.loads(delta_path.read_text()) - if delta.get("release") != EXPECTED_LOCK["release"]: - raise SystemExit(f"unexpected release in {delta_path}") - - base_path = ptoas_root / "tools" / delta.get("base_contract", "") - base = json.loads(base_path.read_text()) - base_operations = dict(base.get("operations", {})) - operations = dict(base_operations) - for name in delta.get("removed_public_operations", []): - if operations.pop(name, None) is None: - raise SystemExit(f"missing removed base operation {name} in {base_path}") - for name, contract in delta.get("added_operations", {}).items(): - if name in operations: - raise SystemExit(f"duplicate added operation {name} in {delta_path}") - operations[name] = contract - for name, operands in delta.get("isa_operand_overrides", {}).items(): - if name not in operations: - raise SystemExit(f"operand override references unknown operation {name}") - operations[name] = dict(operations[name]) - operations[name]["isa_operands"] = operands + contract_path = ptoas_root / "tools/pto_isa_v0_58_1_operation_contracts.json" + contract_file = json.loads(contract_path.read_text()) + for field in ( + "release", + "encoding_abi", + "encoding_projection_sha256", + "source_commit", + "source_tree", + ): + if contract_file.get(field) != EXPECTED_LOCK[field]: + raise SystemExit(f"unexpected {field} in {contract_path}") + operations = dict(contract_file.get("operations", {})) expected_count = EXPECTED_LOCK["tile_operation_count"] - if delta.get("public_operation_count") != expected_count or len(operations) != expected_count: - raise SystemExit(f"unexpected public operation count in {delta_path}") - - dialect_only_entries = dict(base.get("dialect_only_operations", {})) - for name in delta.get("moved_to_dialect_only_operations", []): - contract = base_operations.get(name) - if contract is None: - raise SystemExit(f"missing dialect-only migration source {name} in {base_path}") - dialect_only_entries[contract["ptoas_mnemonic"]] = contract["ptoas_arguments"] + if contract_file.get("public_operation_count") != expected_count or len(operations) != expected_count: + raise SystemExit(f"unexpected public operation count in {contract_path}") + + dialect_only_entries = dict(contract_file.get("dialect_only_operations", {})) dialect_only = { normalize(mnemonic): { "ptoas_mnemonic": mnemonic, @@ -263,14 +311,14 @@ def load_expected_contracts(ptoas_root: Path) -> tuple[dict[str, dict], dict[str for mnemonic, arguments in dialect_only_entries.items() } if len(dialect_only) != len(dialect_only_entries): - raise SystemExit(f"duplicate dialect-only operation names in {base_path}") + raise SystemExit(f"duplicate dialect-only operation names in {contract_path}") public_mnemonics = { normalize(entry["ptoas_mnemonic"]) for entry in operations.values() } overlap = sorted(public_mnemonics & set(dialect_only)) if overlap: raise SystemExit( - f"public/dialect-only operation overlap in {delta_path}: {overlap}" + f"public/dialect-only operation overlap in {contract_path}: {overlap}" ) return operations, dialect_only @@ -310,25 +358,19 @@ def validate_linx_target_surface(ptoas_root: Path) -> None: ): if token not in cli_text and token not in lowering_text: errors.append(f"missing Linx target implementation token: {token}") - delta = json.loads( - (ptoas_root / "tools/pto_isa_v0_58_0_operation_contracts.json").read_text() - ) - base = json.loads( - (ptoas_root / "tools" / delta["base_contract"]).read_text() + contracts = json.loads( + (ptoas_root / "tools/pto_isa_v0_58_1_operation_contracts.json").read_text() ) - for name in delta["removed_public_operations"]: - contract = base["operations"][name] - mnemonic = f'"pto.{contract["ptoas_mnemonic"]}"' + for mnemonic_name in contracts.get("linx_rejected_mnemonics", []): + mnemonic = f'"pto.{mnemonic_name}"' if mnemonic not in cli_text: - errors.append( - f"Linx target boundary does not explicitly reject dialect-only {mnemonic}" - ) + errors.append(f"Linx target boundary does not explicitly reject dialect-only {mnemonic}") if 'ValueRange{dst, peerTid, src}' not in lowering_text: errors.append("GMOV lowering must match Linx-TileOP-API order (dst, peer_tid, src)") if 'ValueRange{dst, src0, src1, src2}' not in lowering_text: errors.append("TFMA lowering must match Linx-TileOP-API order (dst, src0, src1, src2)") if errors: - raise SystemExit("invalid PTOAS Linx v0.58 target surface:\n " + "\n ".join(errors)) + raise SystemExit("invalid PTOAS Linx v0.58.1 target surface:\n " + "\n ".join(errors)) def main() -> int: @@ -357,7 +399,7 @@ def main() -> int: ) boundary_error = bool(deleted_present) if deleted_present: - print("PTOAS has deleted PTO ISA 0.58.0 names active in the dialect:") + print("PTOAS has deleted PTO ISA 0.58.1 names active in the dialect:") for name in deleted_present: print(f" - {name}") @@ -392,14 +434,14 @@ def main() -> int: f"expected PTOAS arguments {expected}, got {actual}" ) if contract_errors: - print("PTOAS has incorrect PTO ISA 0.58.0 operation roles/arity:") + print("PTOAS has incorrect PTO ISA 0.58.1 operation roles/arity:") for error in contract_errors: print(f" - {error}") return 1 if args.linx_root is None: print( - "PTOAS v0.58.0 PTO lock/dialect check OK: all 109 public " + "PTOAS v0.58.1 PTO lock/dialect check OK: all 109 public " f"operation argument contracts and {len(expected_dialect_only)} explicit " "dialect-only contracts match; hardware numeric profile/vectors are " "identity metadata only (execution conformance not evaluated)" @@ -407,11 +449,12 @@ def main() -> int: return 0 manifest = load_manifest(args.linx_root.resolve()) + validate_linx_identity(args.linx_root.resolve(), lock) expected_names = set(expected_public) actual_names = set(manifest) if actual_names != expected_names: - print("LinxISA v0.58.0 public operation boundary mismatch:") + print("LinxISA v0.58.1 public operation boundary mismatch:") for name in sorted(expected_names - actual_names): print(f" - missing manifest operation: {name}") for name in sorted(actual_names - expected_names): @@ -421,6 +464,10 @@ def main() -> int: role_errors = [] for name, contract in expected_public.items(): entry = manifest[name] + if entry.get("engine") != contract.get("engine"): + role_errors.append( + f"{name}: expected engine {contract.get('engine')}, got {entry.get('engine')}" + ) actual = tuple( (operand.get("field"), operand.get("role")) for operand in entry.get("operands", []) @@ -434,13 +481,13 @@ def main() -> int: f"{name}: expected ISA operands {expected}, got {actual}" ) if role_errors: - print("LinxISA v0.58.0 manifest role/arity mismatch:") + print("LinxISA v0.58.1 manifest role/arity mismatch:") for error in role_errors: print(f" - {error}") return 1 print( - "PTOAS v0.58.0 PTO manifest contract check OK: " + "PTOAS v0.58.1 PTO manifest contract check OK: " f"all {len(manifest)} public operations match exact Linx roles/arity; " f"{len(expected_dialect_only)} dialect-only operations are explicitly bounded; " "hardware numeric profile/vectors are identity metadata only " diff --git a/tools/pto_isa_v0_58_1_lock.json b/tools/pto_isa_v0_58_1_lock.json new file mode 100644 index 0000000000..f38970e806 --- /dev/null +++ b/tools/pto_isa_v0_58_1_lock.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://docs.openclaw.ai/schemas/linxisa/pto_spec_lock.v1.json", + "catalogs": { + "command_forms": { + "path": "spec/catalog/command-forms.json", + "sha256": "300a3a57a8728e6c4770da6fff0202b372ec2830edb8dc978dc141d1c26424d0", + "count": 74 + }, + "scalar_forms": { + "path": "spec/catalog/scalar-forms.json", + "sha256": "9f3841d568ffa73fcb43bf4fd365d3c4dba42d27acffa7e273e0f403c0f0c602", + "count": 474 + }, + "tile_operations": { + "path": "spec/catalog/tile-operations.json", + "sha256": "f163dea8be281fd67173713d373b60f95a9c3c4e558adcdf8034cc213507a1a3", + "count": 109 + }, + "extension_encoding_reservations": { + "path": "spec/catalog/extension-encoding-reservations.json", + "sha256": "bdb82b839b98984779d9a1394f6b308f141052ef0b520e5bedb8e87dadd883d4", + "count": 32 + } + }, + "content_sha256": "693e8c0734b48598ac35ffe7fe6f2a01037788fba30ebe026895808d23139f2c", + "encoding_abi": "pto-isa-0.58.1-mode-function-v1", + "encoding_projection_sha256": "89b872d6eaf0252200bc9349d49b9346e2a69d894cdcc2dcd0fd71911c1e0b8c", + "hardware_conformance_profile": { + "path": "spec/hardware-conformance-profile.json", + "profile_id": "pto-hardware-numeric-0.58.1-ieee-v1", + "sha256": "170deadacb174c933c287231fb67da1046d7989f84b6852bf353d68a495d1755" + }, + "numeric_conformance_vectors": { + "path": "spec/evidence/pto-isa-0581-hardware-numeric-vectors.json", + "sha256": "59c96cc2f45f8e8f3eebb8230338b21ec3a77a99e8fb5e1c7c7b391819a6aa81" + }, + "release": "0.58.1", + "release_manifest": { + "path": "spec/release-manifest.json", + "sha256": "acea87af67301173e6d1c6e04014a8dc6e2f658cd2992ed658ab2589cddc7841" + }, + "source": { + "commit": "c381465b2b8e457e162a4246ee58bb9a2c5b49fd", + "tree": "463a19db3d6ba70022f18bdbca0d4b2c6ed586e4", + "repository": "https://github.com/PTO-ISA/pto-spec.git" + } +} diff --git a/tools/pto_isa_v0_58_1_operation_contracts.json b/tools/pto_isa_v0_58_1_operation_contracts.json new file mode 100644 index 0000000000..c6cd8dba10 --- /dev/null +++ b/tools/pto_isa_v0_58_1_operation_contracts.json @@ -0,0 +1,2831 @@ +{ + "release": "0.58.1", + "encoding_abi": "pto-isa-0.58.1-mode-function-v1", + "encoding_projection_sha256": "89b872d6eaf0252200bc9349d49b9346e2a69d894cdcc2dcd0fd71911c1e0b8c", + "source_commit": "c381465b2b8e457e162a4246ee58bb9a2c5b49fd", + "source_tree": "463a19db3d6ba70022f18bdbca0d4b2c6ed586e4", + "public_operation_count": 109, + "operations": { + "GMOV": { + "ptoas_mnemonic": "gmov", + "ptoas_arguments": [ + "src", + "peer_tid", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "resolved-peer-source" + ], + [ + "scalar0", + "peer-tid" + ] + ], + "engine": "TLSU" + }, + "MGATHER": { + "ptoas_mnemonic": "mgather", + "ptoas_arguments": [ + "mem", + "idx", + "dst", + "gatherOob" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "address", + "base-address" + ], + [ + "source0", + "indices" + ] + ], + "engine": "TLSU" + }, + "MGATHER_CAS": { + "ptoas_mnemonic": "mgather_cas", + "ptoas_arguments": [ + "mem", + "idx", + "expected", + "replacement", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "address", + "base-address" + ], + [ + "source0", + "indices" + ], + [ + "source1", + "expected" + ], + [ + "source2", + "replacement" + ] + ], + "engine": "TLSU" + }, + "MGATHER_MASK": { + "ptoas_mnemonic": "mgather_mask", + "ptoas_arguments": [ + "mem", + "idx", + "mask", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "address", + "base-address" + ], + [ + "source0", + "indices" + ], + [ + "source1", + "mask" + ] + ], + "engine": "TLSU" + }, + "MSCATTER": { + "ptoas_mnemonic": "mscatter", + "ptoas_arguments": [ + "src", + "idx", + "mem", + "scatterAtomicOp", + "scatterOob" + ], + "isa_operands": [ + [ + "address", + "base-address" + ], + [ + "source0", + "source" + ], + [ + "source1", + "byte-displacement-indices" + ] + ], + "engine": "TLSU" + }, + "MSCATTER_MASK": { + "ptoas_mnemonic": "mscatter_mask", + "ptoas_arguments": [ + "src", + "idx", + "mask", + "mem" + ], + "isa_operands": [ + [ + "address", + "base-address" + ], + [ + "source0", + "source" + ], + [ + "source1", + "byte-displacement-indices" + ], + [ + "source2", + "exact-predicate-mask" + ] + ], + "engine": "TLSU" + }, + "TABS": { + "ptoas_mnemonic": "tabs", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ] + ], + "engine": "VEC" + }, + "TADD": { + "ptoas_mnemonic": "tadd", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TADDS": { + "ptoas_mnemonic": "tadds", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TAND": { + "ptoas_mnemonic": "tand", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TANDS": { + "ptoas_mnemonic": "tands", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TCI": { + "ptoas_mnemonic": "tci", + "ptoas_arguments": [ + "S", + "dst", + "descending" + ], + "isa_operands": [ + [ + "destination0", + "new Local S32, S16, U32, or U16 destination" + ], + [ + "scalar0", + "typed sequence start" + ], + [ + "flag0", + "ascending or descending direction" + ] + ], + "engine": "SFU" + }, + "TCMP": { + "ptoas_mnemonic": "tcmp", + "ptoas_arguments": [ + "src0", + "src1", + "dst", + "cmpMode" + ], + "isa_operands": [ + [ + "destination0", + "predicate destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ], + [ + "comparison", + "comparison" + ] + ], + "engine": "VEC" + }, + "TCMPS": { + "ptoas_mnemonic": "tcmps", + "ptoas_arguments": [ + "src", + "scalar", + "cmpMode", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new packed Local predicate destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ], + [ + "comparison", + "six-mode comparison" + ] + ], + "engine": "VEC" + }, + "TCOLARGMAX": { + "ptoas_mnemonic": "tcolargmax", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local U32 index destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TCOLARGMIN": { + "ptoas_mnemonic": "tcolargmin", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local U32 index destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPAND": { + "ptoas_mnemonic": "tcolexpand", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDADD": { + "ptoas_mnemonic": "tcolexpandadd", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDDIV": { + "ptoas_mnemonic": "tcolexpanddiv", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDEXPDIF": { + "ptoas_mnemonic": "tcolexpandexpdif", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDMAX": { + "ptoas_mnemonic": "tcolexpandmax", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDMIN": { + "ptoas_mnemonic": "tcolexpandmin", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDMUL": { + "ptoas_mnemonic": "tcolexpandmul", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLEXPANDSUB": { + "ptoas_mnemonic": "tcolexpandsub", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-row broadcast source" + ] + ], + "engine": "SFU" + }, + "TCOLMAX": { + "ptoas_mnemonic": "tcolmax", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TCOLMIN": { + "ptoas_mnemonic": "tcolmin", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TCOLPROD": { + "ptoas_mnemonic": "tcolprod", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TCOLSUM": { + "ptoas_mnemonic": "tcolsum", + "ptoas_arguments": [ + "src", + "tmp", + "dst", + "isBinary" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TCONCAT": { + "ptoas_mnemonic": "tconcat", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "SFU" + }, + "TCVT": { + "ptoas_mnemonic": "tcvt", + "ptoas_arguments": [ + "src", + "dst", + "rmode", + "sat_mode" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ], + [ + "numeric_control", + "rounding-and-saturation" + ] + ], + "engine": "VEC" + }, + "TDEQUANT": { + "ptoas_mnemonic": "tdequant", + "ptoas_arguments": [ + "src", + "scale", + "offset", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new FP32 destination" + ], + [ + "source0", + "persistent S8 or U8 source" + ], + [ + "scalar0", + "positive finite FP32 multiplier" + ], + [ + "scalar1", + "source-typed integer zero point" + ], + [ + "numeric_control", + "rounding" + ] + ], + "engine": "SFU" + }, + "TDIV": { + "ptoas_mnemonic": "tdiv", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "numerator" + ], + [ + "source1", + "denominator" + ] + ], + "engine": "SFU" + }, + "TDIVS": { + "ptoas_mnemonic": "tdivs", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "SFU" + }, + "TEXP": { + "ptoas_mnemonic": "texp", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local floating destination" + ], + [ + "source0", + "persistent Local floating source" + ] + ], + "engine": "SFU" + }, + "TEXPANDS": { + "ptoas_mnemonic": "texpands", + "ptoas_arguments": [ + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TEXTRACT": { + "ptoas_mnemonic": "textract", + "ptoas_arguments": [ + "src", + "indexRow", + "indexCol", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ], + [ + "natural0", + "row-offset" + ], + [ + "natural1", + "column-offset" + ] + ], + "engine": "SFU" + }, + "TFILLPAD": { + "ptoas_mnemonic": "tfillpad", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ], + [ + "scalar0", + "padding" + ] + ], + "engine": "SFU" + }, + "TFMA": { + "ptoas_mnemonic": "tfma", + "ptoas_arguments": [ + "src0", + "src1", + "src2", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "multiplicand-left" + ], + [ + "source1", + "multiplicand-right" + ], + [ + "source2", + "addend" + ] + ], + "engine": "VEC" + }, + "TGATHER": { + "ptoas_mnemonic": "tgather", + "ptoas_arguments": [ + "src", + "dst", + "cdst", + "indices", + "tmp", + "kValue", + "maskPattern", + "cmpMode", + "offset" + ], + "isa_operands": [ + [ + "destination0", + "new Local value destination" + ], + [ + "source0", + "persistent Local value source" + ], + [ + "source1", + "persistent Local S32 or U32 row-index source" + ] + ], + "engine": "SFU" + }, + "TGEMV": { + "ptoas_mnemonic": "tgemv", + "ptoas_arguments": [ + "lhs", + "rhs", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left-vector" + ], + [ + "source1", + "right-matrix" + ] + ], + "engine": "CUBE" + }, + "TGEMV_ACC": { + "ptoas_mnemonic": "tgemv.acc", + "ptoas_arguments": [ + "acc_in", + "lhs", + "rhs", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "accumulator" + ], + [ + "source1", + "left-vector" + ], + [ + "source2", + "right-matrix" + ] + ], + "engine": "CUBE" + }, + "TGEMV_BIAS": { + "ptoas_mnemonic": "tgemv.bias", + "ptoas_arguments": [ + "a", + "b", + "bias", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left-vector" + ], + [ + "source1", + "right-matrix" + ], + [ + "source2", + "bias" + ] + ], + "engine": "CUBE" + }, + "TGEMV_MX": { + "ptoas_mnemonic": "tgemv.mx", + "ptoas_arguments": [ + "a", + "a_scale", + "b", + "b_scale", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left-vector" + ], + [ + "source1", + "row-scale" + ], + [ + "source2", + "right-matrix" + ], + [ + "source3", + "column-scale" + ] + ], + "engine": "CUBE" + }, + "TGEMV_MX_ACC": { + "ptoas_mnemonic": "tgemv.mx.acc", + "ptoas_arguments": [ + "c_in", + "a", + "a_scale", + "b", + "b_scale", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "accumulator" + ], + [ + "source1", + "left-vector" + ], + [ + "source2", + "row-scale" + ], + [ + "source3", + "right-matrix" + ], + [ + "source4", + "column-scale" + ] + ], + "engine": "CUBE" + }, + "TGEMV_MX_BIAS": { + "ptoas_mnemonic": "tgemv.mx.bias", + "ptoas_arguments": [ + "a", + "a_scale", + "b", + "b_scale", + "bias", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left-vector" + ], + [ + "source1", + "row-scale" + ], + [ + "source2", + "right-matrix" + ], + [ + "source3", + "column-scale" + ], + [ + "source4", + "bias" + ] + ], + "engine": "CUBE" + }, + "THISTOGRAM": { + "ptoas_mnemonic": "thistogram", + "ptoas_arguments": [ + "src", + "idx", + "dst", + "isMSB" + ], + "isa_operands": [ + [ + "destination0", + "new Local U32 prefix-histogram destination" + ], + [ + "source0", + "persistent Local U16 or U32 source" + ], + [ + "source1", + "persistent Local U8 prefix filter" + ], + [ + "selected_byte", + "B.DATR ByteId zero through three" + ] + ], + "engine": "SFU" + }, + "TIMG2COL": { + "ptoas_mnemonic": "timg2col", + "ptoas_arguments": [ + "src", + "posM", + "posK", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local Matrix destination" + ], + [ + "source0", + "persistent Local Matrix feature-map source" + ], + [ + "natural0", + "unsigned low-sixteen-bit posM" + ], + [ + "natural1", + "unsigned low-sixteen-bit posK" + ] + ], + "engine": "SFU" + }, + "TINSERT": { + "ptoas_mnemonic": "tinsert", + "ptoas_arguments": [ + "src", + "indexRow", + "indexCol", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "persistent old destination" + ], + [ + "source1", + "persistent insertion source" + ], + [ + "natural0", + "row-offset" + ], + [ + "natural1", + "column-offset" + ] + ], + "engine": "SFU" + }, + "TLOAD": { + "ptoas_mnemonic": "tload", + "ptoas_arguments": [ + "src", + "dst", + "pad_mode", + "pad_value", + "left_padding_num", + "right_padding_num", + "init_out_buffer", + "init_condition" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "address", + "base-address" + ], + [ + "scalar0", + "row-stride-elements" + ] + ], + "engine": "TLSU" + }, + "TLOG": { + "ptoas_mnemonic": "tlog", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local floating destination" + ], + [ + "source0", + "persistent Local floating source" + ] + ], + "engine": "SFU" + }, + "TMATMUL": { + "ptoas_mnemonic": "tmatmul", + "ptoas_arguments": [ + "lhs", + "rhs", + "dst", + "accPhase" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left" + ], + [ + "source1", + "right" + ] + ], + "engine": "CUBE" + }, + "TMATMUL_ACC": { + "ptoas_mnemonic": "tmatmul.acc", + "ptoas_arguments": [ + "acc_in", + "lhs", + "rhs", + "dst", + "accPhase" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "accumulator" + ], + [ + "source1", + "left" + ], + [ + "source2", + "right" + ] + ], + "engine": "CUBE" + }, + "TMATMUL_BIAS": { + "ptoas_mnemonic": "tmatmul.bias", + "ptoas_arguments": [ + "a", + "b", + "bias", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left" + ], + [ + "source1", + "right" + ], + [ + "source2", + "bias" + ] + ], + "engine": "CUBE" + }, + "TMATMUL_MX": { + "ptoas_mnemonic": "tmatmul.mx", + "ptoas_arguments": [ + "a", + "a_scale", + "b", + "b_scale", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left" + ], + [ + "source1", + "row-scale" + ], + [ + "source2", + "right" + ], + [ + "source3", + "column-scale" + ] + ], + "engine": "CUBE" + }, + "TMATMUL_MX_ACC": { + "ptoas_mnemonic": "tmatmul.mx.acc", + "ptoas_arguments": [ + "c_in", + "a", + "a_scale", + "b", + "b_scale", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "accumulator" + ], + [ + "source1", + "left" + ], + [ + "source2", + "row-scale" + ], + [ + "source3", + "right" + ], + [ + "source4", + "column-scale" + ] + ], + "engine": "CUBE" + }, + "TMATMUL_MX_BIAS": { + "ptoas_mnemonic": "tmatmul.mx.bias", + "ptoas_arguments": [ + "a", + "a_scale", + "b", + "b_scale", + "bias", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "left" + ], + [ + "source1", + "row-scale" + ], + [ + "source2", + "right" + ], + [ + "source3", + "column-scale" + ], + [ + "source4", + "bias" + ] + ], + "engine": "CUBE" + }, + "TMAX": { + "ptoas_mnemonic": "tmax", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TMAXS": { + "ptoas_mnemonic": "tmaxs", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TMIN": { + "ptoas_mnemonic": "tmin", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TMINS": { + "ptoas_mnemonic": "tmins", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TMOV": { + "ptoas_mnemonic": "tmov", + "ptoas_arguments": [ + "src", + "dst", + "fp", + "preQuantScalar", + "accToVecMode", + "reluPreMode" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ] + ], + "engine": "TLSU" + }, + "TMRGSORT": { + "ptoas_mnemonic": "tmrgsort", + "ptoas_arguments": [ + "srcs", + "blockLen", + "dsts", + "tmp", + "excuted", + "exhausted" + ], + "isa_operands": [ + [ + "destination0", + "new Local merged destination" + ], + [ + "source0", + "persistent sorted Local left source" + ], + [ + "source1", + "persistent sorted Local right source" + ], + [ + "flag0", + "ascending or descending selection" + ] + ], + "engine": "SFU" + }, + "TMUL": { + "ptoas_mnemonic": "tmul", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TMULS": { + "ptoas_mnemonic": "tmuls", + "ptoas_arguments": [ + "src0", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TNEG": { + "ptoas_mnemonic": "tneg", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ] + ], + "engine": "VEC" + }, + "TNOT": { + "ptoas_mnemonic": "tnot", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ] + ], + "engine": "VEC" + }, + "TOR": { + "ptoas_mnemonic": "tor", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TORS": { + "ptoas_mnemonic": "tors", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TPARTADD": { + "ptoas_mnemonic": "tpartadd", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local union destination" + ], + [ + "source0", + "persistent Local left source anchored at origin" + ], + [ + "source1", + "persistent Local right source anchored at origin" + ] + ], + "engine": "SFU" + }, + "TPARTMAX": { + "ptoas_mnemonic": "tpartmax", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local union destination" + ], + [ + "source0", + "persistent Local left source anchored at origin" + ], + [ + "source1", + "persistent Local right source anchored at origin" + ] + ], + "engine": "SFU" + }, + "TPARTMIN": { + "ptoas_mnemonic": "tpartmin", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local union destination" + ], + [ + "source0", + "persistent Local left source anchored at origin" + ], + [ + "source1", + "persistent Local right source anchored at origin" + ] + ], + "engine": "SFU" + }, + "TPARTMUL": { + "ptoas_mnemonic": "tpartmul", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local union destination" + ], + [ + "source0", + "persistent Local left source anchored at origin" + ], + [ + "source1", + "persistent Local right source anchored at origin" + ] + ], + "engine": "SFU" + }, + "TPREFETCH": { + "ptoas_mnemonic": "tprefetch", + "ptoas_arguments": [ + "address", + "row_stride", + "valid_cols", + "valid_rows", + "physical_cols" + ], + "isa_operands": [ + [ + "address", + "base-address" + ], + [ + "scalar0", + "row-stride-elements" + ], + [ + "positive0", + "valid-columns" + ], + [ + "positive1", + "valid-rows" + ], + [ + "positive2", + "physical-columns" + ] + ], + "engine": "TLSU" + }, + "TQUANT": { + "ptoas_mnemonic": "tquant", + "ptoas_arguments": [ + "src", + "fp", + "offset", + "dst", + "quant_type" + ], + "isa_operands": [ + [ + "destination0", + "new S8 or U8 destination" + ], + [ + "source0", + "persistent FP32 source" + ], + [ + "scalar0", + "positive finite FP32 multiplier" + ], + [ + "scalar1", + "destination-typed integer zero point" + ], + [ + "numeric_control", + "rounding and saturation" + ] + ], + "engine": "SFU" + }, + "TRECIP": { + "ptoas_mnemonic": "trecip", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local floating destination" + ], + [ + "source0", + "persistent Local floating source" + ] + ], + "engine": "SFU" + }, + "TRELU": { + "ptoas_mnemonic": "trelu", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ] + ], + "engine": "VEC" + }, + "TREM": { + "ptoas_mnemonic": "trem", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "dividend" + ], + [ + "source1", + "divisor" + ] + ], + "engine": "SFU" + }, + "TREMS": { + "ptoas_mnemonic": "trems", + "ptoas_arguments": [ + "src", + "scalar", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "SFU" + }, + "TROWARGMAX": { + "ptoas_mnemonic": "trowargmax", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local U32 index destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TROWARGMIN": { + "ptoas_mnemonic": "trowargmin", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local U32 index destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TROWEXPAND": { + "ptoas_mnemonic": "trowexpand", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDADD": { + "ptoas_mnemonic": "trowexpandadd", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDDIV": { + "ptoas_mnemonic": "trowexpanddiv", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDEXPDIF": { + "ptoas_mnemonic": "trowexpandexpdif", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDMAX": { + "ptoas_mnemonic": "trowexpandmax", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDMIN": { + "ptoas_mnemonic": "trowexpandmin", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDMUL": { + "ptoas_mnemonic": "trowexpandmul", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWEXPANDSUB": { + "ptoas_mnemonic": "trowexpandsub", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local full-shape numeric source" + ], + [ + "source1", + "persistent Local one-column broadcast source" + ] + ], + "engine": "SFU" + }, + "TROWMAX": { + "ptoas_mnemonic": "trowmax", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TROWMIN": { + "ptoas_mnemonic": "trowmin", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TROWPROD": { + "ptoas_mnemonic": "trowprod", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TROWSUM": { + "ptoas_mnemonic": "trowsum", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local same-type numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ] + ], + "engine": "SFU" + }, + "TRSQRT": { + "ptoas_mnemonic": "trsqrt", + "ptoas_arguments": [ + "src", + "dst", + "tmp" + ], + "isa_operands": [ + [ + "destination0", + "new Local floating destination" + ], + [ + "source0", + "persistent Local floating source" + ] + ], + "engine": "SFU" + }, + "TSCATTER": { + "ptoas_mnemonic": "tscatter", + "ptoas_arguments": [ + "src", + "indexes", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new zero-initialized Local value destination" + ], + [ + "source0", + "persistent Local value source" + ], + [ + "source1", + "persistent Local width-matched row-index source" + ] + ], + "engine": "SFU" + }, + "TSEL": { + "ptoas_mnemonic": "tsel", + "ptoas_arguments": [ + "mask", + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "numeric destination" + ], + [ + "source0", + "packed predicate mask" + ], + [ + "source1", + "source-true" + ], + [ + "source2", + "source-false" + ] + ], + "engine": "VEC" + }, + "TSELS": { + "ptoas_mnemonic": "tsels", + "ptoas_arguments": [ + "mask", + "src", + "tmp", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "packed Local predicate mask" + ], + [ + "source1", + "persistent Local source selected by one" + ], + [ + "scalar0", + "per-participating-PE scalar selected by zero" + ] + ], + "engine": "VEC" + }, + "TSHL": { + "ptoas_mnemonic": "tshl", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "value-source" + ], + [ + "source1", + "shift-count-source" + ] + ], + "engine": "VEC" + }, + "TSHLS": { + "ptoas_mnemonic": "tshls", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TSHR": { + "ptoas_mnemonic": "tshr", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "value-source" + ], + [ + "source1", + "shift-count-source" + ] + ], + "engine": "VEC" + }, + "TSHRS": { + "ptoas_mnemonic": "tshrs", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TSORT": { + "ptoas_mnemonic": "tsort", + "ptoas_arguments": [ + "src", + "dst", + "dst_indices", + "descending" + ], + "isa_operands": [ + [ + "destination0", + "new Local sorted-value destination" + ], + [ + "destination1", + "new Local U32 original-index destination" + ], + [ + "source0", + "persistent Local source" + ], + [ + "sort_width", + "LB0 row-group width" + ], + [ + "flag0", + "ascending or descending selection" + ] + ], + "engine": "SFU" + }, + "TSQRT": { + "ptoas_mnemonic": "tsqrt", + "ptoas_arguments": [ + "src", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local floating destination" + ], + [ + "source0", + "persistent Local floating source" + ] + ], + "engine": "SFU" + }, + "TSTORE": { + "ptoas_mnemonic": "tstore", + "ptoas_arguments": [ + "src", + "dst", + "preQuantScalar", + "stPhase", + "atomicType", + "reluPreMode" + ], + "isa_operands": [ + [ + "address", + "base-address" + ], + [ + "scalar0", + "row-stride-elements" + ], + [ + "source0", + "source" + ] + ], + "engine": "TLSU" + }, + "TSUB": { + "ptoas_mnemonic": "tsub", + "ptoas_arguments": [ + "src0", + "src1", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TSUBS": { + "ptoas_mnemonic": "tsubs", + "ptoas_arguments": [ + "src", + "scalar", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + }, + "TTRANS": { + "ptoas_mnemonic": "ttrans", + "ptoas_arguments": [ + "src", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source" + ] + ], + "engine": "SFU" + }, + "TTRI": { + "ptoas_mnemonic": "ttri", + "ptoas_arguments": [ + "diagonal", + "dst", + "upperOrLower" + ], + "isa_operands": [ + [ + "destination0", + "new Local triangular destination" + ], + [ + "flag0", + "lower or upper orientation" + ], + [ + "diagonal", + "signed diagonal displacement" + ] + ], + "engine": "SFU" + }, + "TXOR": { + "ptoas_mnemonic": "txor", + "ptoas_arguments": [ + "src0", + "src1", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "destination" + ], + [ + "source0", + "source-left" + ], + [ + "source1", + "source-right" + ] + ], + "engine": "VEC" + }, + "TXORS": { + "ptoas_mnemonic": "txors", + "ptoas_arguments": [ + "src", + "scalar", + "tmp", + "dst" + ], + "isa_operands": [ + [ + "destination0", + "new Local numeric destination" + ], + [ + "source0", + "persistent Local numeric source" + ], + [ + "scalar0", + "per-participating-PE private-GPR scalar" + ] + ], + "engine": "VEC" + } + }, + "linx_rejected_mnemonics": [ + "acccvt", + "talloc", + "taxpy", + "tdeinterleave", + "tfree", + "tgatherb", + "tinterleave", + "tpartargmax", + "tpartargmin", + "tpop", + "tprelu", + "tpush", + "treshape" + ], + "dialect_only_operations": { + "acccvt": [ + "dst" + ], + "talloc": [ + "entry", + "pipe_handle", + "split" + ], + "talloc_to_aic": [ + "id", + "split" + ], + "talloc_to_aiv": [ + "id", + "split" + ], + "taxpy": [ + "src", + "scalar", + "dst" + ], + "tconcatidx": [ + "src0", + "src1", + "src0Idx", + "src1Idx", + "dst" + ], + "tdeinterleave": [ + "src", + "dst0", + "dst1" + ], + "textract_fp": [ + "src", + "fp", + "indexRow", + "indexCol", + "dst" + ], + "tfillpad_expand": [ + "src", + "dst" + ], + "tfillpad_inplace": [ + "src", + "dst" + ], + "tfree": [ + "entry", + "pipe_handle", + "split" + ], + "tfree_from_aic": [ + "entry", + "id", + "split" + ], + "tfree_from_aiv": [ + "entry", + "id", + "split" + ], + "tgatherb": [ + "src", + "offsets", + "dst" + ], + "tget_scale_addr": [ + "src", + "dst" + ], + "tgetval": [ + "src", + "offset" + ], + "tinsert_fp": [ + "src", + "fp", + "indexRow", + "indexCol", + "dst" + ], + "tinterleave": [ + "src0", + "src1", + "dst" + ], + "tmov.fp": [ + "src", + "fp", + "dst" + ], + "tpartargmax": [ + "src0", + "src1", + "src0Idx", + "src1Idx", + "dst", + "dstIdx" + ], + "tpartargmin": [ + "src0", + "src1", + "src0Idx", + "src1Idx", + "dst", + "dstIdx" + ], + "tpop": [ + "tile", + "pipe_handle", + "split" + ], + "tpop_from_aic": [ + "valid_row", + "valid_col", + "id", + "split" + ], + "tpop_from_aiv": [ + "valid_row", + "valid_col", + "id", + "split" + ], + "tprelu": [ + "src0", + "src1", + "tmp", + "dst" + ], + "tprint": [ + "src" + ], + "tpush": [ + "tile", + "pipe_handle", + "split" + ], + "tpush_to_aic": [ + "tile", + "id", + "split" + ], + "tpush_to_aiv": [ + "tile", + "id", + "split" + ], + "treshape": [ + "src" + ], + "tsetval": [ + "dst", + "offset", + "val" + ], + "tstore_fp": [ + "src", + "fp", + "dst" + ], + "tsync": [ + "events" + ] + } +} diff --git a/tools/ptoas/ptoas.cpp b/tools/ptoas/ptoas.cpp index 2863350929..6c9d5f5e52 100644 --- a/tools/ptoas/ptoas.cpp +++ b/tools/ptoas/ptoas.cpp @@ -56,7 +56,7 @@ using namespace pto; #endif static void printPTOASVersion(llvm::raw_ostream &os) { - os << "ptoas " << PTOAS_RELEASE_VERSION << "\n"; + os << "ptoas " << PTOAS_RELEASE_VERSION << " (PTO ISA 0.58.1)\n"; } static LogicalResult reorderEmitCFunctions(ModuleOp module) { @@ -263,7 +263,7 @@ static LogicalResult validateLinxOperationBoundary(ModuleOp module) { StringRef name = op->getName().getStringRef(); op->emitError() << "'" << name << "' op is dialect-only and not part of the active Linx " - "PTO ISA v0.58.0 target"; + "PTO ISA v0.58.1 target"; } return invalid.empty() ? success() : failure(); } @@ -554,12 +554,15 @@ static void materializeControlFlowOperands(Operation *rootOp) { if (!initAttr) continue; - Value tmp = - builder.create(op->getLoc(), value.getType(), - initAttr) - .getResult(); + Value tmp = builder + .create( + op->getLoc(), + emitc::LValueType::get(value.getType()), initAttr) + .getResult(); builder.create(op->getLoc(), tmp, value); - operand.set(tmp); + Value loaded = + builder.create(op->getLoc(), value.getType(), tmp); + operand.set(loaded); } } } diff --git a/tools/ptobc/MAINTENANCE.md b/tools/ptobc/MAINTENANCE.md index b6a51129cc..ac2baf1fb4 100644 --- a/tools/ptobc/MAINTENANCE.md +++ b/tools/ptobc/MAINTENANCE.md @@ -9,11 +9,26 @@ If you change any of the following: …then you **must** update the PTO-BC v0 opcode/schema tables (regenerate `tools/ptobc/generated/ptobc_opcodes_v0.h`) and ensure tests pass. +For the PTO ISA 0.58.1 hard break, the opcode assignments remain unchanged and +the audited schema transformation changes only TIMG2COL from 2 to 4 operands +and TPREFETCH from 2 to 5 operands. Apply or verify that transformation with: + +```bash +python3 tools/ptobc/update_v0581_schema.py \ + tools/ptobc/generated/ptobc_opcodes_v0.h --write +python3 tools/ptobc/update_v0581_schema.py \ + tools/ptobc/generated/ptobc_opcodes_v0.h +``` + +The second command is the non-mutating CI audit. Historical 0.58.0 opcode +assignments and their round-trip test remain release evidence. + ## Required gates Run (or rely on CI): - `ctest -R ptobc_stage9_e2e` - `ctest -R ptobc_to_ptoas_smoke` - `ctest -R ptobc_opcode_coverage_check` +- `ctest -R ptobc_v0581_contract_encode` ## Notes - `ptobc_opcode_coverage_check` is a heuristic based on `mnemonic = "..."` occurrences. diff --git a/tools/ptobc/generated/ptobc_opcodes_v0.h b/tools/ptobc/generated/ptobc_opcodes_v0.h index 457938d8e7..6594f784a9 100644 --- a/tools/ptobc/generated/ptobc_opcodes_v0.h +++ b/tools/ptobc/generated/ptobc_opcodes_v0.h @@ -6,7 +6,8 @@ // INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. // See LICENSE in the root of the software repository for the full text of the License. -// Generated by docs/bytecode/tools/gen_v0_tables.py +// Generated base table plus audited 0.58.1 schema transformation; see +// tools/ptobc/update_v0581_schema.py and tools/ptobc/MAINTENANCE.md. #pragma once #include @@ -120,7 +121,7 @@ inline constexpr OpInfo kOpTable[] = { {0x1029, "pto.tgatherb", 0, 0x00, 0x00, 3, 0, 0, 0x00}, {0x102A, "pto.tgemv", 1, 0x00, 0x01, 0, 0, 0, 0x00}, {0x102B, "pto.tgetval", 0, 0x01, 0x00, 2, 1, 0, 0x00}, - {0x102C, "pto.timg2col", 0, 0x00, 0x00, 2, 0, 0, 0x00}, + {0x102C, "pto.timg2col", 0, 0x00, 0x00, 4, 0, 0, 0x00}, {0x102D, "pto.tinsert", 0, 0x00, 0x00, 4, 0, 0, 0x00}, {0x102E, "pto.tinsert_fp", 0, 0x00, 0x00, 5, 0, 0, 0x00}, {0x102F, "pto.tload", 0, 0x00, 0x00, 2, 0, 0, 0x00}, @@ -144,7 +145,7 @@ inline constexpr OpInfo kOpTable[] = { {0x1042, "pto.tpartmax", 0, 0x00, 0x00, 3, 0, 0, 0x00}, {0x1043, "pto.tpartmin", 0, 0x00, 0x00, 3, 0, 0, 0x00}, {0x1044, "pto.tpartmul", 0, 0x00, 0x00, 3, 0, 0, 0x00}, - {0x1045, "pto.tprefetch", 0, 0x00, 0x00, 2, 0, 0, 0x00}, + {0x1045, "pto.tprefetch", 0, 0x00, 0x00, 5, 0, 0, 0x00}, {0x1046, "pto.tprelu", 0, 0x00, 0x00, 4, 0, 0, 0x00}, {0x1047, "pto.tquant", 0, 0x00, 0x02, 3, 0, 0, 0x00}, {0x1048, "pto.trecip", 0, 0x00, 0x00, 2, 0, 0, 0x00}, diff --git a/tools/ptobc/src/ptobc_decode_print.cpp b/tools/ptobc/src/ptobc_decode_print.cpp index fd08f36c31..d79e2ee96a 100644 --- a/tools/ptobc/src/ptobc_decode_print.cpp +++ b/tools/ptobc/src/ptobc_decode_print.cpp @@ -530,21 +530,23 @@ static void addImmediateAttrs(BuildCtx &bc, mlir::OperationState &state, const KnownOpImmediates &imms) { switch (info.imm_kind) { case 0x01: - state.addAttribute("predicate", - mlir::arith::CmpIPredicateAttr::get( - bc.ctx, mlir::arith::CmpIPredicate(imms.cmpPred))); + state.attributes.set( + "predicate", mlir::arith::CmpIPredicateAttr::get( + bc.ctx, mlir::arith::CmpIPredicate(imms.cmpPred))); return; case 0x02: - state.addAttribute("src_op", mlir::pto::SyncOpTypeAttr::get( - bc.ctx, mlir::pto::SyncOpType(imms.evA))); - state.addAttribute("dst_op", mlir::pto::SyncOpTypeAttr::get( - bc.ctx, mlir::pto::SyncOpType(imms.evB))); - state.addAttribute("event_id", - mlir::pto::EventAttr::get(bc.ctx, - mlir::pto::EVENT(imms.evC))); + state.attributes.set( + "src_op", mlir::pto::SyncOpTypeAttr::get( + bc.ctx, mlir::pto::SyncOpType(imms.evA))); + state.attributes.set( + "dst_op", mlir::pto::SyncOpTypeAttr::get( + bc.ctx, mlir::pto::SyncOpType(imms.evB))); + state.attributes.set( + "event_id", + mlir::pto::EventAttr::get(bc.ctx, mlir::pto::EVENT(imms.evC))); return; case 0x05: - state.addAttribute("value", buildConstAttr(bc, imms.constId)); + state.attributes.set("value", buildConstAttr(bc, imms.constId)); return; default: return; diff --git a/tools/ptobc/tests/CMakeLists.txt b/tools/ptobc/tests/CMakeLists.txt index 76f543e865..eb3344cf00 100644 --- a/tools/ptobc/tests/CMakeLists.txt +++ b/tools/ptobc/tests/CMakeLists.txt @@ -44,6 +44,14 @@ add_test(NAME ptobc_v0580_ops_v0_encode bash ${CMAKE_CURRENT_LIST_DIR}/v0580_ops_v0_encode.sh ) +add_test(NAME ptobc_v0581_contract_encode + COMMAND ${CMAKE_COMMAND} -E env + PTOBC_BIN=$ + TEST_INPUT_DIR=${CMAKE_SOURCE_DIR}/test/lit/pto + OUT_DIR=${CMAKE_CURRENT_BINARY_DIR}/ptobc_v0581_contract_out + bash ${CMAKE_CURRENT_LIST_DIR}/ptobc_v0581_contract_encode.sh +) + add_test(NAME ptobc_trowexpandsub_v0_encode COMMAND ${CMAKE_COMMAND} -E env PTOBC_BIN=$ diff --git a/tools/ptobc/tests/opcode_coverage_check.py b/tools/ptobc/tests/opcode_coverage_check.py index 5254f64f26..7747e661d5 100755 --- a/tools/ptobc/tests/opcode_coverage_check.py +++ b/tools/ptobc/tests/opcode_coverage_check.py @@ -58,10 +58,22 @@ "pto.tfma": (0x10A2, 4), } +# PTO-BC serializes MLIR SSA operands, not duplicated architectural roles. +# TINSERT therefore has four bytecode operands (src, row, col, dst), while its +# five-role Linx contract and emitted call are dst, dst, src, row, col. +V0581_EXACT_BYTECODE_SSA_ARITY = { + "pto.trowexpand": 2, + "pto.tcolexpand": 2, + "pto.tconcat": 3, + "pto.timg2col": 4, + "pto.tinsert": 4, + "pto.tprefetch": 5, +} + EXACT_ARITY = { - "pto.tprefetch": 2, **{name: arity for name, (_, arity) in NEW_V0571_OPCODE_ASSIGNMENTS.items()}, **{name: arity for name, (_, arity) in NEW_V0580_OPCODE_ASSIGNMENTS.items()}, + **V0581_EXACT_BYTECODE_SSA_ARITY, } PRE_V0571_MAX_PTO_OPCODE = 0x109B diff --git a/tools/ptobc/tests/ptobc_v0581_contract_encode.sh b/tools/ptobc/tests/ptobc_v0581_contract_encode.sh new file mode 100755 index 0000000000..932b7c5228 --- /dev/null +++ b/tools/ptobc/tests/ptobc_v0581_contract_encode.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + +set -euo pipefail + +: "${PTOBC_BIN:?PTOBC_BIN not set}" +: "${TEST_INPUT_DIR:?TEST_INPUT_DIR not set}" + +OUT_DIR=${OUT_DIR:-"${PWD}/ptobc_v0581_contract_out"} +mkdir -p "${OUT_DIR}" + +input="${TEST_INPUT_DIR}/v0581_linx_contract.pto" +bytecode="${OUT_DIR}/v0581_linx_contract.ptobc" +decoded="${OUT_DIR}/v0581_linx_contract.roundtrip.pto" +"${PTOBC_BIN}" encode "${input}" -o "${bytecode}" +"${PTOBC_BIN}" decode "${bytecode}" -o "${decoded}" + +grep -F "pto.timg2col ins(" "${decoded}" >/dev/null +grep -F "pto.tinsert ins(" "${decoded}" >/dev/null +grep -F "pto.tprefetch ins(" "${decoded}" >/dev/null diff --git a/tools/ptobc/update_v0581_schema.py b/tools/ptobc/update_v0581_schema.py new file mode 100755 index 0000000000..c8277c0822 --- /dev/null +++ b/tools/ptobc/update_v0581_schema.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. +"""Apply or audit the PTO-BC v0 schema delta for PTO ISA 0.58.1.""" + +from __future__ import annotations + +import argparse +import re +from pathlib import Path + + +ARITY_UPDATES = { + "pto.timg2col": (0x102C, 2, 4), + "pto.tprefetch": (0x1045, 2, 5), +} + + +def transform(text: str) -> str: + for name, (opcode, old_arity, new_arity) in ARITY_UPDATES.items(): + old = ( + f'{{0x{opcode:04X}, "{name}", 0, 0x00, 0x00, ' + f'{old_arity}, 0, 0, 0x00}}' + ) + new = ( + f'{{0x{opcode:04X}, "{name}", 0, 0x00, 0x00, ' + f'{new_arity}, 0, 0, 0x00}}' + ) + if old in text: + text = text.replace(old, new, 1) + elif new not in text: + raise SystemExit(f"missing audited PTO-BC row for {name}") + return text + + +def audit(text: str) -> None: + rows = { + name: (int(opcode, 16), int(arity)) + for opcode, name, arity in re.findall( + r'\{(0x[0-9A-Fa-f]+),\s*"([^"]+)",\s*\d+,\s*' + r'0x[0-9A-Fa-f]+,\s*0x[0-9A-Fa-f]+,\s*(\d+),', + text, + ) + } + for name, (opcode, _old_arity, new_arity) in ARITY_UPDATES.items(): + if rows.get(name) != (opcode, new_arity): + raise SystemExit( + f"{name}: expected opcode/arity {(opcode, new_arity)}, got {rows.get(name)}" + ) + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("header", type=Path) + parser.add_argument("--write", action="store_true") + args = parser.parse_args() + + original = args.header.read_text() + updated = transform(original) + audit(updated) + if args.write: + args.header.write_text(updated) + elif updated != original: + raise SystemExit("PTO-BC header needs the audited 0.58.1 schema transformation") + print("PTO-BC v0 0.58.1 schema audit OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/test_check_v058_pto_manifest.py b/tools/test_check_v058_pto_manifest.py new file mode 100755 index 0000000000..7edf32e621 --- /dev/null +++ b/tools/test_check_v058_pto_manifest.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + +import importlib.util +import json +import tempfile +import unittest +from pathlib import Path + + +MODULE_PATH = Path(__file__).with_name("check_v058_pto_manifest.py") +SPEC = importlib.util.spec_from_file_location("check_v058_pto_manifest", MODULE_PATH) +CHECKER = importlib.util.module_from_spec(SPEC) +assert SPEC.loader is not None +SPEC.loader.exec_module(CHECKER) + + +class LinxIdentityTest(unittest.TestCase): + def test_mismatched_release_fails_closed(self): + local_lock = json.loads( + Path(__file__).with_name("pto_isa_v0_58_1_lock.json").read_text() + ) + mismatched = json.loads(json.dumps(local_lock)) + mismatched["release"] = "0.58.0" + with tempfile.TemporaryDirectory() as directory: + linx_root = Path(directory) + lock_path = linx_root / "isa/v0.58/pto-spec.lock.json" + lock_path.parent.mkdir(parents=True) + lock_path.write_text(json.dumps(mismatched)) + with self.assertRaisesRegex(SystemExit, "PTOAS/root PTO ISA lock mismatch"): + CHECKER.validate_linx_identity(linx_root, local_lock) + + +if __name__ == "__main__": + unittest.main() From bb481085964fc9a2b6d181c4a856e7600a7d3a85 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 01:03:49 +0800 Subject: [PATCH 13/32] fix: close PTO ISA 0.58.1 review gaps --- .dockerignore | 6 + .github/workflows/build_wheel.yml | 13 +- .github/workflows/build_wheel_mac.yml | 13 +- .github/workflows/isa_contract.yml | 18 +++ CMakeLists.txt | 9 ++ docker/Dockerfile | 10 +- docs/PTO_IR_manual.md | 16 +- include/PTO/IR/PTO.h | 6 + include/PTO/IR/PTOOps.td | 6 + .../PTO/Transforms/InsertSync/SyncCommon.h | 7 +- lib/PTO/IR/PTO.cpp | 144 +++++++++++++++--- .../Transforms/InsertSync/InsertSyncDebug.cpp | 2 + .../Transforms/InsertSync/PTOIRTranslator.cpp | 5 + lib/PTO/Transforms/InsertSync/SyncCodegen.cpp | 5 + lib/PTO/Transforms/PTOToEmitC.cpp | 8 + test/lit/pto/emitc_void_lvalue_supported.pto | 14 ++ .../tprefetch_verify_oversized_byte_count.pto | 2 +- test/lit/pto/v0581_linx_sfu_pipeline.pto | 18 +++ .../v0581_linx_static_legality_negative.pto | 63 ++++++++ test/lit/pto/v0581_tdiv_arch_distinction.pto | 14 ++ tools/check_emitc_lvalue_contract.py | 27 ++++ tools/check_release_delivery_contract.py | 39 +++++ tools/check_v058_pto_manifest.py | 24 ++- tools/test_check_v058_pto_manifest.py | 22 +++ 24 files changed, 440 insertions(+), 51 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/isa_contract.yml create mode 100644 test/lit/pto/emitc_void_lvalue_supported.pto create mode 100644 test/lit/pto/v0581_linx_sfu_pipeline.pto create mode 100644 test/lit/pto/v0581_linx_static_legality_negative.pto create mode 100644 test/lit/pto/v0581_tdiv_arch_distinction.pto create mode 100644 tools/check_emitc_lvalue_contract.py create mode 100644 tools/check_release_delivery_contract.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..8cced6f09b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +.git +build +install +wheelhouse +__pycache__ +*.pyc diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 8b230a6283..3fb2d2852b 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -63,15 +63,10 @@ jobs: run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then EXPECTED_RELEASE_VERSION="$(${PY_PATH}/bin/python .github/scripts/compute_ptoas_version.py --mode release)" - if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.1" ]; then - ${PY_PATH}/bin/python tools/check_v058_pto_manifest.py --ptoas-root . - PTOAS_VERSION="${EXPECTED_RELEASE_VERSION}" - else - PTOAS_VERSION="${GITHUB_REF_NAME#v}" - if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then - echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 - exit 1 - fi + PTOAS_VERSION="${GITHUB_REF_NAME#v}" + if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then + echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 + exit 1 fi else PTOAS_VERSION="$(${PY_PATH}/bin/python .github/scripts/compute_ptoas_version.py --mode dev)" diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index f015e3031a..a95d6a0893 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -66,15 +66,10 @@ jobs: run: | if [ "${GITHUB_EVENT_NAME}" = "release" ]; then EXPECTED_RELEASE_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode release)" - if [ "${GITHUB_REF_NAME}" = "linxisa-v0.58.1" ]; then - python tools/check_v058_pto_manifest.py --ptoas-root . - PTOAS_VERSION="${EXPECTED_RELEASE_VERSION}" - else - PTOAS_VERSION="${GITHUB_REF_NAME#v}" - if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then - echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 - exit 1 - fi + PTOAS_VERSION="${GITHUB_REF_NAME#v}" + if [ "${PTOAS_VERSION}" != "${EXPECTED_RELEASE_VERSION}" ]; then + echo "release tag '${GITHUB_REF_NAME}' does not match computed version '${EXPECTED_RELEASE_VERSION}'" >&2 + exit 1 fi else PTOAS_VERSION="$(python .github/scripts/compute_ptoas_version.py --mode dev)" diff --git a/.github/workflows/isa_contract.yml b/.github/workflows/isa_contract.yml new file mode 100644 index 0000000000..be25cbe8a4 --- /dev/null +++ b/.github/workflows/isa_contract.yml @@ -0,0 +1,18 @@ +name: PTO ISA v0.58.1 contract + +on: + push: + tags: + - linxisa-v0.58.1 + workflow_dispatch: + +permissions: + contents: read + +jobs: + exact-contract: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check exact PTO ISA contract + run: python3 tools/check_v058_pto_manifest.py --ptoas-root . diff --git a/CMakeLists.txt b/CMakeLists.txt index 03c5bfb988..3551f3a9ae 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,15 @@ if(BUILD_TESTING) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_python_binding_link_contract.py --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) + add_test( + NAME release_delivery_contract_check + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_release_delivery_contract.py + --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) + add_test( + NAME emitc_lvalue_contract_check + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_emitc_lvalue_contract.py) add_subdirectory(tools/ptobc/tests) endif() diff --git a/docker/Dockerfile b/docker/Dockerfile index e310763e5c..f547e9a94d 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -32,6 +32,11 @@ ENV LLVM_BUILD_DIR=$LLVM_SOURCE_DIR/build-shared ENV PTO_SOURCE_DIR=$WORKSPACE_DIR/PTOAS ENV PTO_INSTALL_DIR=$PTO_SOURCE_DIR/install +# Build the reviewed checkout supplied as the Docker build context. This keeps +# PTOAS source identity coupled to the caller's exact checkout instead of an +# unrelated fork's mutable default branch. +COPY . ${PTO_SOURCE_DIR} + # build LLVM WORKDIR $WORKSPACE_DIR RUN git clone --no-checkout ${LLVM_REPOSITORY} ${LLVM_SOURCE_DIR} \ @@ -52,11 +57,6 @@ RUN cmake -C /tmp/LinuxHardeningCache.cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR RUN ninja -C $LLVM_BUILD_DIR -# build ptoas -WORKDIR $WORKSPACE_DIR -RUN git clone https://github.com/zhangstevenunity/PTOAS.git -# TODO: tag git commit of PTOAS repo - WORKDIR $PTO_SOURCE_DIR RUN cmake -C /tmp/LinuxHardeningCache.cmake -G Ninja \ -S . \ diff --git a/docs/PTO_IR_manual.md b/docs/PTO_IR_manual.md index 2abb049813..c0ca602d54 100644 --- a/docs/PTO_IR_manual.md +++ b/docs/PTO_IR_manual.md @@ -810,6 +810,17 @@ The detailed cache placement is target-defined, but access faults are architectural rather than optional hint behavior. Unlike most generated PTO intrinsic wrappers, `TPREFETCH` does not add implicit wait-event synchronization. +For Linx ISA 0.58.1, statically known `valid_cols`, `valid_rows`, and +`physical_cols` values must be in `1..65535`. `physical_cols` must also be a +power of two and at least `valid_cols`. Dynamic values are accepted by the IR; +the producer must guarantee the same constraints at runtime before execution. +`row_stride` must be nonnegative when static and has the same runtime +precondition when dynamic. + +For `TIMG2COL`, statically known `posM` and `posK` values must be in +`0..65535`. Dynamic positions are accepted by the IR and must satisfy that +range at runtime. + **Arguments:** | Name | Type | Description | @@ -826,7 +837,10 @@ queue value. **Constraints & Verification:** - `address` must have exactly type `i64`. -- Shape and stride operands have `index` type and must be nonnegative. +- Shape and stride operands have `index` type. `row_stride` must be + nonnegative; `valid_cols`, `valid_rows`, and `physical_cols` must be in + `1..65535`; and `physical_cols` must be a power of two no smaller than + `valid_cols`. Dynamic operands carry the equivalent runtime preconditions. - A partition view or tile destination is not a compatibility spelling and is rejected by the PTOAS parser/verifier. diff --git a/include/PTO/IR/PTO.h b/include/PTO/IR/PTO.h index d31ab2a82a..5d9835e33d 100644 --- a/include/PTO/IR/PTO.h +++ b/include/PTO/IR/PTO.h @@ -42,6 +42,12 @@ #include "PTO/IR/PTOEnums.h.inc" +namespace mlir::pto { +/// Architectural Linx tile engines. This is intentionally distinct from +/// PIPE, which describes the Ascend synchronization-pipe ABI. +enum class LinxEngine { VEC, SFU, TLSU, CUBE }; +} // namespace mlir::pto + //===----------------------------------------------------------------------===// // PTO Interfaces //===----------------------------------------------------------------------===// diff --git a/include/PTO/IR/PTOOps.td b/include/PTO/IR/PTOOps.td index 2033b647d1..38cdcb74be 100644 --- a/include/PTO/IR/PTOOps.td +++ b/include/PTO/IR/PTOOps.td @@ -3725,6 +3725,7 @@ def TDivOp : PTO_TOp<"tdiv", [ let extraClassDeclaration = [{ ::mlir::pto::PIPE getPipe() { return ::mlir::pto::PIPE::PIPE_V; } + ::mlir::pto::LinxEngine getLinxEngine() { return ::mlir::pto::LinxEngine::SFU; } ::mlir::MutableOperandRange getDpsInitsMutable() { return getDstMutable(); } }]; @@ -3757,6 +3758,7 @@ def TDivSOp : PTO_TOp<"tdivs", [ let extraClassDeclaration = [{ ::mlir::pto::PIPE getPipe() { return ::mlir::pto::PIPE::PIPE_V; } + ::mlir::pto::LinxEngine getLinxEngine() { return ::mlir::pto::LinxEngine::SFU; } ::mlir::MutableOperandRange getDpsInitsMutable() { return getDstMutable(); } }]; @@ -4216,6 +4218,8 @@ def TImg2ColOp : PTO_TOp<"timg2col", [ let results = (outs); + let hasVerifier = 1; + let assemblyFormat = [{ `ins` `(` $src `,` $posM `,` $posK `:` qualified(type($src)) `,` type($posM) `,` type($posK) `)` `outs` `(` $dst `:` qualified(type($dst) ) `)` @@ -5106,6 +5110,7 @@ def TRemOp: PTO_TOp<"trem", [ let extraClassDeclaration = [{ ::mlir::pto::PIPE getPipe() { return ::mlir::pto::PIPE::PIPE_V; } + ::mlir::pto::LinxEngine getLinxEngine() { return ::mlir::pto::LinxEngine::SFU; } ::mlir::MutableOperandRange getDpsInitsMutable() { return getDstMutable(); } }]; } @@ -5139,6 +5144,7 @@ def TRemSOp: PTO_TOp<"trems", [ let extraClassDeclaration = [{ ::mlir::pto::PIPE getPipe() { return ::mlir::pto::PIPE::PIPE_V; } + ::mlir::pto::LinxEngine getLinxEngine() { return ::mlir::pto::LinxEngine::SFU; } ::mlir::MutableOperandRange getDpsInitsMutable() { return getDstMutable(); } }]; } diff --git a/include/PTO/Transforms/InsertSync/SyncCommon.h b/include/PTO/Transforms/InsertSync/SyncCommon.h index 09aa4dc9d6..1e8ceea22a 100644 --- a/include/PTO/Transforms/InsertSync/SyncCommon.h +++ b/include/PTO/Transforms/InsertSync/SyncCommon.h @@ -62,8 +62,11 @@ enum class PipelineType : uint32_t { // 虚拟 Pipe (如果有) VIRTUAL_PIPE_MTE2_L1A = 11, VIRTUAL_PIPE_MTE2_L1B = 12, - - PIPE_NUM = 13, + + // Linx architectural engines. Keep these distinct from the Ascend PIPE ABI. + LINX_SFU = 13, + + PIPE_NUM = 14, PIPE_UNASSIGNED = 99, PIPE_LAST = PIPE_UNASSIGNED }; diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index feb631835d..3eb97315b1 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -91,6 +91,7 @@ static std::optional getPTOMemorySpaceEnum(Type ty); enum class VerifierTargetArch { A2A3, A5, + Linx, }; static VerifierTargetArch getVerifierTargetArch(Operation *op); static std::optional getVerifierArchName(Operation *op); @@ -337,16 +338,17 @@ uint64_t mlir::pto::F4E2M1x2Type::getPreferredAlignment( static VerifierTargetArch getVerifierTargetArch(Operation *op) { if (auto archName = getVerifierArchName(op)) { - return (archName->equals_insensitive("a5") || - archName->equals_insensitive("linx")) - ? VerifierTargetArch::A5 - : VerifierTargetArch::A2A3; + if (archName->equals_insensitive("linx")) + return VerifierTargetArch::Linx; + return archName->equals_insensitive("a5") ? VerifierTargetArch::A5 + : VerifierTargetArch::A2A3; } switch (getPTOParserTargetArch(op ? op->getContext() : nullptr)) { case PTOParserTargetArch::A5: - case PTOParserTargetArch::Linx: return VerifierTargetArch::A5; + case PTOParserTargetArch::Linx: + return VerifierTargetArch::Linx; case PTOParserTargetArch::A3: case PTOParserTargetArch::Unspecified: return VerifierTargetArch::A2A3; @@ -365,10 +367,7 @@ static std::optional getVerifierArchName(Operation *op) { } static bool isVerifierTargetLinx(Operation *op) { - if (auto archName = getVerifierArchName(op)) - return archName->equals_insensitive("linx"); - return getPTOParserTargetArch(op ? op->getContext() : nullptr) == - PTOParserTargetArch::Linx; + return getVerifierTargetArch(op) == VerifierTargetArch::Linx; } static bool shouldBypassDecodedMemrefVerifier(Operation *op) { @@ -391,9 +390,10 @@ static SmallVector canonicalizeTileBufValidShape(ArrayRef v return canonical; } -template +template static LogicalResult dispatchVerifierByArch(Operation *op, FnA2A3 &&verifyA2A3, - FnA5 &&verifyA5) { + FnA5 &&verifyA5, + FnLinx &&verifyLinx) { if (shouldBypassDecodedMemrefVerifier(op)) return success(); switch (getVerifierTargetArch(op)) { @@ -401,10 +401,23 @@ static LogicalResult dispatchVerifierByArch(Operation *op, FnA2A3 &&verifyA2A3, return verifyA2A3(); case VerifierTargetArch::A5: return verifyA5(); + case VerifierTargetArch::Linx: + return verifyLinx(); } return failure(); } +template +static LogicalResult dispatchVerifierByArch(Operation *op, FnA2A3 &&verifyA2A3, + FnA5 &&verifyA5) { + return dispatchVerifierByArch( + op, std::forward(verifyA2A3), std::forward(verifyA5), + [&]() -> LogicalResult { + return op->emitOpError( + "Linx legality is not implemented for this operation"); + }); +} + static ParseResult parseSyncEventOpCommon(OpAsmParser &parser, OperationState &result, StringAttr pipeAttrName, @@ -2328,13 +2341,35 @@ LogicalResult TLoadOp::verify() { LogicalResult TPrefetchOp::verify() { if (!getAddress().getType().isInteger(64)) return emitOpError("expects address to have type i64"); - constexpr std::array operandNames = { - "row_stride", "valid_cols", "valid_rows", "physical_cols"}; - SmallVector operands = {getRowStride(), getValidCols(), - getValidRows(), getPhysicalCols()}; - for (auto [name, value] : llvm::zip_equal(operandNames, operands)) { - if (auto constant = getConstantIntegerValue(value); constant && *constant < 0) - return emitOpError() << "expects " << name << " to be nonnegative"; + if (auto rowStride = getConstantIntegerValue(getRowStride()); + rowStride && *rowStride < 0) + return emitOpError("expects row_stride to be nonnegative"); + auto verifyU16Positive = [&](StringRef name, Value value) -> LogicalResult { + if (auto constant = getConstantIntegerValue(value); + constant && (*constant < 1 || *constant > 65535)) + return emitOpError() << "expects " << name << " in range [1, 65535]"; + return success(); + }; + if (failed(verifyU16Positive("valid_cols", getValidCols())) || + failed(verifyU16Positive("valid_rows", getValidRows())) || + failed(verifyU16Positive("physical_cols", getPhysicalCols()))) + return failure(); + auto physicalCols = getConstantIntegerValue(getPhysicalCols()); + if (physicalCols && !llvm::isPowerOf2_64(static_cast(*physicalCols))) + return emitOpError("expects physical_cols to be a power of two"); + auto validCols = getConstantIntegerValue(getValidCols()); + if (physicalCols && validCols && *physicalCols < *validCols) + return emitOpError("expects physical_cols to be at least valid_cols"); + return success(); +} + +LogicalResult TImg2ColOp::verify() { + for (auto [name, value] : + llvm::zip_equal(std::array{"posM", "posK"}, + std::array{getPosM(), getPosK()})) { + if (auto constant = getConstantIntegerValue(value); + constant && (*constant < 0 || *constant > 65535)) + return emitOpError() << "expects " << name << " in range [0, 65535]"; } return success(); } @@ -3499,6 +3534,8 @@ static LogicalResult verifyVecTileCommon(Operation *op, Type ty, StringRef name) return verifyVecTileCommonA2A3(op, ty, name); case VerifierTargetArch::A5: return verifyVecTileCommonA5(op, ty, name); + case VerifierTargetArch::Linx: + return verifyVecTileCommonA2A3(op, ty, name); } return failure(); } @@ -3539,6 +3576,8 @@ static LogicalResult verifyAccTileCommon(Operation *op, Type ty, StringRef name) return verifyAccTileCommonA2A3(op, ty, name); case VerifierTargetArch::A5: return verifyAccTileCommonA5(op, ty, name); + case VerifierTargetArch::Linx: + return op->emitOpError("Linx ACC tile legality is not implemented"); } return failure(); } @@ -3618,6 +3657,8 @@ static LogicalResult verifyMatTileOperands(Operation *op, Type lhsTy, Type rhsTy case VerifierTargetArch::A5: return verifyMatTileOperandsA5(op, lhsTy, rhsTy, dstTy, allowLowPrecisionInputs); + case VerifierTargetArch::Linx: + return op->emitOpError("Linx CUBE operand legality is not implemented"); } return failure(); } @@ -3679,6 +3720,8 @@ static LogicalResult verifyGemvTileOperands(Operation *op, Type lhsTy, Type rhsT case VerifierTargetArch::A5: return verifyGemvTileOperandsA5(op, lhsTy, rhsTy, dstTy, allowLowPrecisionInputs); + case VerifierTargetArch::Linx: + return op->emitOpError("Linx CUBE GEMV legality is not implemented"); } return failure(); } @@ -3720,6 +3763,8 @@ static LogicalResult verifyMatBiasTile(Operation *op, Type biasTy, Type dstTy, return verifyMatBiasTileA2A3(op, biasTy, dstTy, requireFloatBias); case VerifierTargetArch::A5: return verifyMatBiasTileA5(op, biasTy, dstTy, requireFloatBias); + case VerifierTargetArch::Linx: + return op->emitOpError("Linx CUBE bias legality is not implemented"); } return failure(); } @@ -4733,7 +4778,18 @@ LogicalResult mlir::pto::TDivOp::verify() { return emitOpError("expects A5 tdiv element type to be i32/i16/f16/f32"); return success(); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + auto verifyLinx = [&]() -> LogicalResult { + FailureOr elemOr = verifyMatchingRowMajorBinaryTileOpCommon( + getOperation(), getSrc0().getType(), getSrc1().getType(), + getDst().getType()); + if (failed(elemOr)) + return failure(); + if (!elemOr->isF16() && !elemOr->isF32()) + return emitOpError("expects Linx SFU tdiv element type to be f16/f32"); + return success(); + }; + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5, + verifyLinx); } mlir::LogicalResult mlir::pto::TDivSOp::verify() { @@ -4780,7 +4836,18 @@ mlir::LogicalResult mlir::pto::TDivSOp::verify() { }; auto verifyA2A3 = [&]() -> LogicalResult { return verifyByArch(PTOArch::A3); }; auto verifyA5 = [&]() -> LogicalResult { return verifyByArch(PTOArch::A5); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + auto verifyLinx = [&]() -> LogicalResult { + if (failed(verifyByArch(PTOArch::Linx))) + return failure(); + Type tileTy = isTileLike(getSrc().getType()) ? getSrc().getType() + : getScalar().getType(); + Type elem = getElemTy(tileTy); + if (!elem.isF16() && !elem.isF32()) + return emitOpError("expects Linx SFU tdivs element type to be f16/f32"); + return success(); + }; + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5, + verifyLinx); } mlir::LogicalResult mlir::pto::TExpOp::verify() { @@ -5125,7 +5192,24 @@ mlir::LogicalResult mlir::pto::TInsertOp::verify() { "expects A5 tinsert to use a supported src/dst loc pair: " "acc->mat, vec->mat, or vec->vec"); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + auto verifyLinx = [&]() -> LogicalResult { + auto common = verifyCommon(); + if (failed(common)) + return failure(); + auto [srcTy, dstTy, srcTb, dstTb, srcElem, dstElem, srcSpace, dstSpace] = + *common; + if (!srcSpace || !dstSpace || *srcSpace != pto::AddressSpace::VEC || + *dstSpace != pto::AddressSpace::VEC) + return emitOpError("expects Linx VEC tinsert to use vec->vec"); + if (!isRowMajorNoneBoxND(srcTb) || !isRowMajorNoneBoxND(dstTb)) + return emitOpError("expects Linx VEC tinsert src/dst to use ND layout"); + if (srcElem != dstElem || !isA2A3VecInsertElemType(srcElem)) + return emitOpError( + "expects Linx VEC tinsert src/dst to have matching i8/f16/bf16/f32 dtype"); + return success(); + }; + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5, + verifyLinx); } static bool isColMajorRowMajorNZTileBuf(pto::TileBufType ty) { @@ -7603,7 +7687,13 @@ mlir::LogicalResult mlir::pto::TRemOp::verify() { return emitOpError("expects A5 trem element type to be i32/i16/f16/f32"); return success(); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + auto verifyLinx = [&]() -> LogicalResult { + if (!elem.isF16() && !elem.isF32()) + return emitOpError("expects Linx SFU trem element type to be f16/f32"); + return success(); + }; + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5, + verifyLinx); } mlir::LogicalResult mlir::pto::TRemSOp::verify() { @@ -7646,7 +7736,13 @@ mlir::LogicalResult mlir::pto::TRemSOp::verify() { return emitOpError("expects A5 trems element type to be i32/i16/f16/f32"); return success(); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + auto verifyLinx = [&]() -> LogicalResult { + if (!elem.isF16() && !elem.isF32()) + return emitOpError("expects Linx SFU trems element type to be f16/f32"); + return success(); + }; + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5, + verifyLinx); } static std::optional getStaticNumElements(ArrayRef shape) { @@ -8899,6 +8995,8 @@ mlir::LogicalResult mlir::pto::TStoreFPOp::verify() { return verifyA2A3(); case VerifierTargetArch::A5: return verifyA5(); + case VerifierTargetArch::Linx: + return emitOpError("Linx legality is not implemented for this operation"); } return failure(); } diff --git a/lib/PTO/Transforms/InsertSync/InsertSyncDebug.cpp b/lib/PTO/Transforms/InsertSync/InsertSyncDebug.cpp index 63bf844919..d5e17c745f 100644 --- a/lib/PTO/Transforms/InsertSync/InsertSyncDebug.cpp +++ b/lib/PTO/Transforms/InsertSync/InsertSyncDebug.cpp @@ -62,6 +62,8 @@ static llvm::StringRef getPipelineName(PipelineType pipe) { return "VIRTUAL_PIPE_MTE2_L1A"; case PipelineType::VIRTUAL_PIPE_MTE2_L1B: return "VIRTUAL_PIPE_MTE2_L1B"; + case PipelineType::LINX_SFU: + return "LINX_SFU"; case PipelineType::PIPE_NUM: return "PIPE_NUM"; case PipelineType::PIPE_UNASSIGNED: diff --git a/lib/PTO/Transforms/InsertSync/PTOIRTranslator.cpp b/lib/PTO/Transforms/InsertSync/PTOIRTranslator.cpp index 0c888b3d8f..523a38b903 100644 --- a/lib/PTO/Transforms/InsertSync/PTOIRTranslator.cpp +++ b/lib/PTO/Transforms/InsertSync/PTOIRTranslator.cpp @@ -563,6 +563,10 @@ void PTOIRTranslator::UpdatePTOOpInfo(Operation *op) { // 6. [P0 修改] 获取 Op 的 Pipeline 类型 // ============================================================================ pto::PipelineType PTOIRTranslator::getOpPipeline(Operation *op) { + if (pto::isTargetArchLinx(op) && + isa(op)) + return pto::PipelineType::LINX_SFU; + // 1. 优先尝试通过接口获取 if (auto pipeOp = dyn_cast(op)) { // 注意:假设 pto::Pipe (ODS Enum) 和 pto::PipelineType (C++ Enum) 的数值定义是一致的 @@ -937,6 +941,7 @@ std::string PTOIRTranslator::getPipelineName(pto::PipelineType pipe) { case pto::PipelineType::PIPE_V: return "VECTOR"; case pto::PipelineType::PIPE_S: return "SCALAR"; case pto::PipelineType::PIPE_ALL: return "BARRIER"; + case pto::PipelineType::LINX_SFU: return "LINX_SFU"; default: return "UNKNOWN"; } } diff --git a/lib/PTO/Transforms/InsertSync/SyncCodegen.cpp b/lib/PTO/Transforms/InsertSync/SyncCodegen.cpp index fea24ba2d9..c26428729e 100644 --- a/lib/PTO/Transforms/InsertSync/SyncCodegen.cpp +++ b/lib/PTO/Transforms/InsertSync/SyncCodegen.cpp @@ -28,6 +28,11 @@ using namespace mlir::pto; // ============================================================================== static pto::PipeAttr getPipeAttr(Builder &builder, PipelineType pipe) { + // The current sync instruction ABI has no SFU token. Lower Linx SFU syncs + // through the scalar synchronization channel only at this ABI boundary; + // dependency analysis keeps LINX_SFU distinct from PIPE_V throughout. + if (pipe == PipelineType::LINX_SFU) + return pto::PipeAttr::get(builder.getContext(), pto::PIPE::PIPE_S); auto odsPipeVal = static_cast(pipe); return pto::PipeAttr::get(builder.getContext(), odsPipeVal); } diff --git a/lib/PTO/Transforms/PTOToEmitC.cpp b/lib/PTO/Transforms/PTOToEmitC.cpp index 9287de2804..57465e4e55 100644 --- a/lib/PTO/Transforms/PTOToEmitC.cpp +++ b/lib/PTO/Transforms/PTOToEmitC.cpp @@ -12511,8 +12511,16 @@ static AICORE inline void ptoas_auto_sync_tail( call.erase(); continue; } + + call.emitError( + "cannot preserve mutable lvalue operands for this void call; " + "unsupported call_opaque argument or template shape"); + signalPassFailure(); + return; } + // Result-producing call_opaque operations are input expressions, so + // converting their lvalues to explicit reads preserves semantics. OpBuilder builder(call); builder.setInsertionPoint(call); for (OpOperand &operand : call->getOpOperands()) { diff --git a/test/lit/pto/emitc_void_lvalue_supported.pto b/test/lit/pto/emitc_void_lvalue_supported.pto new file mode 100644 index 0000000000..7e00c52ecc --- /dev/null +++ b/test/lit/pto/emitc_void_lvalue_supported.pto @@ -0,0 +1,14 @@ +// RUN: ptoas --pto-arch=linx %s | FileCheck %s + +module { + func.func @supported_mutating_lvalues() { + %src0 = pto.alloc_tile : !pto.tile_buf + %src1 = pto.alloc_tile : !pto.tile_buf + %dst = pto.alloc_tile : !pto.tile_buf + pto.tdiv ins(%src0, %src1 : !pto.tile_buf, !pto.tile_buf) outs(%dst : !pto.tile_buf) + return + } +} + +// CHECK: TDIV([[DST:v[0-9]+]], [[SRC0:v[0-9]+]], [[SRC1:v[0-9]+]]); +// CHECK-NOT: PTOAS__TILE_LOAD diff --git a/test/lit/pto/tprefetch_verify_oversized_byte_count.pto b/test/lit/pto/tprefetch_verify_oversized_byte_count.pto index 9e718f3b4b..5c49c62c33 100644 --- a/test/lit/pto/tprefetch_verify_oversized_byte_count.pto +++ b/test/lit/pto/tprefetch_verify_oversized_byte_count.pto @@ -12,4 +12,4 @@ module attributes {pto.target_arch = "a5"} { } } -// CHECK: error: 'pto.tprefetch' op expects physical_cols to be nonnegative +// CHECK: error: 'pto.tprefetch' op expects physical_cols in range [1, 65535] diff --git a/test/lit/pto/v0581_linx_sfu_pipeline.pto b/test/lit/pto/v0581_linx_sfu_pipeline.pto new file mode 100644 index 0000000000..a4eb9b0d45 --- /dev/null +++ b/test/lit/pto/v0581_linx_sfu_pipeline.pto @@ -0,0 +1,18 @@ +// RUN: ptoas --pto-arch=linx --enable-insert-sync %s | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_sfu_pipeline(%a: memref<16x16xf32, #pto.address_space>, + %b: memref<16x16xf32, #pto.address_space>) { + %src0 = memref.alloc() : memref<16x16xf32, #pto.address_space> + %src1 = memref.alloc() : memref<16x16xf32, #pto.address_space> + %dst = memref.alloc() : memref<16x16xf32, #pto.address_space> + pto.tload ins(%a : memref<16x16xf32, #pto.address_space>) outs(%src0 : memref<16x16xf32, #pto.address_space>) {layout = #pto.layout} + pto.tload ins(%b : memref<16x16xf32, #pto.address_space>) outs(%src1 : memref<16x16xf32, #pto.address_space>) {layout = #pto.layout} + pto.tdiv ins(%src0, %src1 : memref<16x16xf32, #pto.address_space>, memref<16x16xf32, #pto.address_space>) outs(%dst : memref<16x16xf32, #pto.address_space>) + return + } +} + +// CHECK: set_flag(PIPE_MTE2, PIPE_S, +// CHECK: wait_flag(PIPE_MTE2, PIPE_S, +// CHECK-NOT: set_flag(PIPE_MTE2, PIPE_V, diff --git a/test/lit/pto/v0581_linx_static_legality_negative.pto b/test/lit/pto/v0581_linx_static_legality_negative.pto new file mode 100644 index 0000000000..02bf63002d --- /dev/null +++ b/test/lit/pto/v0581_linx_static_legality_negative.pto @@ -0,0 +1,63 @@ +// RUN: not ptoas --pto-arch=linx %s 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @tprefetch_zero_valid_cols() { + %address = arith.constant 4096 : i64 + %stride = arith.constant 64 : index + %zero = arith.constant 0 : index + %one = arith.constant 1 : index + %physical = arith.constant 64 : index + pto.tprefetch ins(%address, %stride, %zero, %one, %physical : i64, index, index, index, index) + return + } + func.func @tprefetch_oversized_valid_rows() { + %address = arith.constant 4096 : i64 + %stride = arith.constant 64 : index + %one = arith.constant 1 : index + %oversized = arith.constant 65536 : index + %physical = arith.constant 64 : index + pto.tprefetch ins(%address, %stride, %one, %oversized, %physical : i64, index, index, index, index) + return + } + func.func @tprefetch_non_power_of_two_physical_cols() { + %address = arith.constant 4096 : i64 + %stride = arith.constant 64 : index + %valid = arith.constant 32 : index + %rows = arith.constant 1 : index + %physical = arith.constant 48 : index + pto.tprefetch ins(%address, %stride, %valid, %rows, %physical : i64, index, index, index, index) + return + } + func.func @tprefetch_undersized_physical_cols() { + %address = arith.constant 4096 : i64 + %stride = arith.constant 64 : index + %valid = arith.constant 32 : index + %rows = arith.constant 1 : index + %physical = arith.constant 16 : index + pto.tprefetch ins(%address, %stride, %valid, %rows, %physical : i64, index, index, index, index) + return + } + func.func @timg2col_negative_pos_m() { + %minus_one = arith.constant -1 : index + %zero = arith.constant 0 : index + %src = pto.alloc_tile : !pto.tile_buf + %dst = pto.alloc_tile : !pto.tile_buf + pto.timg2col ins(%src, %minus_one, %zero : !pto.tile_buf, index, index) outs(%dst : !pto.tile_buf) + return + } + func.func @timg2col_oversized_pos_k() { + %zero = arith.constant 0 : index + %oversized = arith.constant 65536 : index + %src = pto.alloc_tile : !pto.tile_buf + %dst = pto.alloc_tile : !pto.tile_buf + pto.timg2col ins(%src, %zero, %oversized : !pto.tile_buf, index, index) outs(%dst : !pto.tile_buf) + return + } +} + +// CHECK-DAG: expects valid_cols in range [1, 65535] +// CHECK-DAG: expects valid_rows in range [1, 65535] +// CHECK-DAG: expects physical_cols to be a power of two +// CHECK-DAG: expects physical_cols to be at least valid_cols +// CHECK-DAG: expects posM in range [0, 65535] +// CHECK-DAG: expects posK in range [0, 65535] diff --git a/test/lit/pto/v0581_tdiv_arch_distinction.pto b/test/lit/pto/v0581_tdiv_arch_distinction.pto new file mode 100644 index 0000000000..e480502f3b --- /dev/null +++ b/test/lit/pto/v0581_tdiv_arch_distinction.pto @@ -0,0 +1,14 @@ +// RUN: ptoas --pto-arch=a5 %s -o /dev/null +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module { + func.func @tdiv_i16_distinguishes_a5_from_linx() { + %src0 = pto.alloc_tile : !pto.tile_buf + %src1 = pto.alloc_tile : !pto.tile_buf + %dst = pto.alloc_tile : !pto.tile_buf + pto.tdiv ins(%src0, %src1 : !pto.tile_buf, !pto.tile_buf) outs(%dst : !pto.tile_buf) + return + } +} + +// CHECK: error: 'pto.tdiv' op expects Linx SFU tdiv element type to be f16/f32 diff --git a/tools/check_emitc_lvalue_contract.py b/tools/check_emitc_lvalue_contract.py new file mode 100644 index 0000000000..883df25667 --- /dev/null +++ b/tools/check_emitc_lvalue_contract.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +"""Guard the fail-closed boundary for void mutating EmitC calls.""" + +from pathlib import Path + + +def main() -> int: + source = (Path(__file__).resolve().parents[1] / "lib/PTO/Transforms/PTOToEmitC.cpp").read_text() + start = source.index("if (call->getNumResults() == 0)") + end = source.index("// Result-producing call_opaque operations", start) + void_path = source[start:end] + required = ( + "cannot preserve mutable lvalue operands for this void call", + "signalPassFailure();", + "return;", + ) + missing = [token for token in required if token not in void_path] + if missing: + raise SystemExit(f"void mutating lvalue path is not fail closed: missing {missing}") + if "create" in void_path: + raise SystemExit("void mutating lvalue path must not materialize input-only loads") + print("EmitC void-lvalue contract OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py new file mode 100644 index 0000000000..74066bb47b --- /dev/null +++ b/tools/check_release_delivery_contract.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +"""Validate reproducible Docker source and ISA-tag workflow behavior.""" + +from __future__ import annotations + +import argparse +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--ptoas-root", type=Path, default=Path(__file__).resolve().parents[1]) + args = parser.parse_args() + root = args.ptoas_root.resolve() + + dockerfile = (root / "docker/Dockerfile").read_text() + if "COPY . ${PTO_SOURCE_DIR}" not in dockerfile: + raise SystemExit("Docker build must COPY the reviewed PTOAS checkout") + if "git clone https://github.com/zhangstevenunity/PTOAS.git" in dockerfile: + raise SystemExit("Docker build must not clone an unpinned PTOAS fork") + + workflow = (root / ".github/workflows/isa_contract.yml").read_text() + if "linxisa-v0.58.1" not in workflow: + raise SystemExit("raw linxisa-v0.58.1 tag is not an ISA checker trigger") + exact_command = "python3 tools/check_v058_pto_manifest.py --ptoas-root ." + if exact_command not in workflow: + raise SystemExit("ISA tag workflow does not run the exact contract checker") + + for name in ("build_wheel.yml", "build_wheel_mac.yml"): + text = (root / ".github/workflows" / name).read_text() + if 'GITHUB_REF_NAME}" = "linxisa-v0.58.1"' in text: + raise SystemExit(f"{name} conflates ISA identity with PTOAS product version") + + print("release delivery contract OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_v058_pto_manifest.py b/tools/check_v058_pto_manifest.py index f621c8ce9f..e25139de9a 100644 --- a/tools/check_v058_pto_manifest.py +++ b/tools/check_v058_pto_manifest.py @@ -342,7 +342,15 @@ def load_ptoas_ops(ptoas_root: Path) -> dict[str, dict]: key = normalize(mnemonic) if key in operations: raise SystemExit(f"duplicate normalized PTOAS mnemonic in {ods_path}: {mnemonic}") - operations[key] = {"mnemonic": mnemonic, "arguments": arguments} + linx_engine = re.search( + r"getLinxEngine\(\).*?LinxEngine::(VEC|SFU|TLSU|CUBE)", body, + re.DOTALL, + ) + operations[key] = { + "mnemonic": mnemonic, + "arguments": arguments, + "linx_engine": linx_engine.group(1) if linx_engine else None, + } return operations @@ -439,6 +447,20 @@ def main() -> int: print(f" - {error}") return 1 + # These four operations moved from the Ascend vector pipe to the Linx SFU. + # Require an explicit executable mapping so PIPE_V cannot silently become + # the effective Linx engine. + for name in ("TDIV", "TDIVS", "TREM", "TREMS"): + actual_engine = ptoas_ops[normalize(expected_public[name]["ptoas_mnemonic"])][ + "linx_engine" + ] + if actual_engine != "SFU": + print( + f"PTOAS effective Linx engine mismatch for {name}: " + f"expected SFU, got {actual_engine or 'unmapped'}" + ) + return 1 + if args.linx_root is None: print( "PTOAS v0.58.1 PTO lock/dialect check OK: all 109 public " diff --git a/tools/test_check_v058_pto_manifest.py b/tools/test_check_v058_pto_manifest.py index 7edf32e621..7f35243351 100755 --- a/tools/test_check_v058_pto_manifest.py +++ b/tools/test_check_v058_pto_manifest.py @@ -36,6 +36,28 @@ def test_mismatched_release_fails_closed(self): with self.assertRaisesRegex(SystemExit, "PTOAS/root PTO ISA lock mismatch"): CHECKER.validate_linx_identity(linx_root, local_lock) + def test_effective_sfu_mapping_is_read_from_ods(self): + ptoas_root = Path(__file__).resolve().parents[1] + operations = CHECKER.load_ptoas_ops(ptoas_root) + for mnemonic in ("TDIV", "TDIVS", "TREM", "TREMS"): + self.assertEqual(operations[mnemonic]["linx_engine"], "SFU") + + def test_pipe_v_only_mapping_is_not_accepted_as_linx_engine(self): + ptoas_root = Path(__file__).resolve().parents[1] + ods = (ptoas_root / "include/PTO/IR/PTOOps.td").read_text() + ods = ods.replace( + "::mlir::pto::LinxEngine getLinxEngine() { return ::mlir::pto::LinxEngine::SFU; }", + "", + 1, + ) + with tempfile.TemporaryDirectory() as directory: + fake_root = Path(directory) + fake_ods = fake_root / "include/PTO/IR/PTOOps.td" + fake_ods.parent.mkdir(parents=True) + fake_ods.write_text(ods) + operations = CHECKER.load_ptoas_ops(fake_root) + self.assertIsNone(operations["TDIV"]["linx_engine"]) + if __name__ == "__main__": unittest.main() From 8adbd7f3f5a281e8c639dd8272a13eab2e5b52df Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 01:24:45 +0800 Subject: [PATCH 14/32] fix: fail closed on Linx decoded forms --- .github/workflows/isa_contract.yml | 23 +++++ CMakeLists.txt | 8 ++ docker/Dockerfile | 10 +- docker/README.md | 9 +- lib/PTO/IR/PTO.cpp | 96 +++++++++++++++++-- ...81_linx_bind_tile_dispatch_fail_closed.pto | 17 ++++ ...inx_direct_memref_verifier_fail_closed.pto | 12 +++ ...v0581_linx_memref_dispatch_fail_closed.pto | 13 +++ .../v0581_linx_non_nd_memref_fail_closed.pto | 13 +++ .../pto/v0581_linx_tload_decoded_contract.pto | 12 +++ tools/check_linx_verifier_contract.py | 25 +++++ tools/check_release_delivery_contract.py | 69 +++++++++++++ tools/test_release_delivery_contract.py | 60 ++++++++++++ 13 files changed, 352 insertions(+), 15 deletions(-) create mode 100644 test/lit/pto/v0581_linx_bind_tile_dispatch_fail_closed.pto create mode 100644 test/lit/pto/v0581_linx_direct_memref_verifier_fail_closed.pto create mode 100644 test/lit/pto/v0581_linx_memref_dispatch_fail_closed.pto create mode 100644 test/lit/pto/v0581_linx_non_nd_memref_fail_closed.pto create mode 100644 test/lit/pto/v0581_linx_tload_decoded_contract.pto create mode 100644 tools/check_linx_verifier_contract.py create mode 100644 tools/test_release_delivery_contract.py diff --git a/.github/workflows/isa_contract.yml b/.github/workflows/isa_contract.yml index be25cbe8a4..9a02601574 100644 --- a/.github/workflows/isa_contract.yml +++ b/.github/workflows/isa_contract.yml @@ -4,6 +4,13 @@ on: push: tags: - linxisa-v0.58.1 + pull_request: + paths: + - docker/** + - .dockerignore + - .github/workflows/isa_contract.yml + - tools/check_release_delivery_contract.py + - tools/test_release_delivery_contract.py workflow_dispatch: permissions: @@ -16,3 +23,19 @@ jobs: - uses: actions/checkout@v4 - name: Check exact PTO ISA contract run: python3 tools/check_v058_pto_manifest.py --ptoas-root . + + docker-builder: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check Docker delivery contract + run: python3 tools/check_release_delivery_contract.py --ptoas-root . + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Build reviewed builder stage + uses: docker/build-push-action@v6 + with: + context: . + file: docker/Dockerfile + target: builder + push: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 3551f3a9ae..51fcfe107d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,14 @@ if(BUILD_TESTING) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_release_delivery_contract.py --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) + add_test( + NAME release_delivery_contract_unit + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_release_delivery_contract.py) + add_test( + NAME linx_decoded_verifier_contract_check + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_linx_verifier_contract.py) add_test( NAME emitc_lvalue_contract_check COMMAND ${Python3_EXECUTABLE} diff --git a/docker/Dockerfile b/docker/Dockerfile index f547e9a94d..8d165e15ef 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -79,7 +79,7 @@ ENV PY_PACKAGE_DIR=$LLVM_BUILD_DIR/tools/mlir/python_packages/mlir_core/ # copy pto.py, _pto_ops_gen.py RUN cp $PTO_INSTALL_DIR/mlir/dialects/*.py $PY_PACKAGE_DIR/mlir/dialects/ -COPY ./setup.py $PY_PACKAGE_DIR/ +COPY docker/setup.py $PY_PACKAGE_DIR/ WORKDIR $PY_PACKAGE_DIR RUN python setup.py bdist_wheel @@ -110,7 +110,7 @@ RUN ptoas --enable-insert-sync ./abs.pto -o ./abs.cpp # collect only *.so actually needed by ptoas (transitive closure under build dirs) # it is smaller (<200 MB) than copying the full $LLVM_BUILD_DIR/lib (>700 MB) ENV PTOAS_DEPS_DIR=/ptoas-deps -COPY copy_ptoas_deps.sh /tmp/copy_ptoas_deps.sh +COPY docker/copy_ptoas_deps.sh /tmp/copy_ptoas_deps.sh RUN mkdir -p $PTOAS_DEPS_DIR && bash /tmp/copy_ptoas_deps.sh # ----------------------------------------------------------------------------- @@ -157,8 +157,10 @@ ARG PTO_ISA_COMMIT=662d7f2a916d6bbde3109ce4a16ed5c28f5d900a # pinned: https://gitcode.com/cann/pto-isa/commit/662d7f2a916d6bbde3109ce4a16ed5c28f5d900a WORKDIR /sources -RUN git clone https://gitcode.com/cann/pto-isa.git \ - && cd pto-isa && git checkout $PTO_ISA_COMMIT +RUN git clone --no-checkout https://gitcode.com/cann/pto-isa.git pto-isa \ + && git -C pto-isa fetch --depth 1 origin ${PTO_ISA_COMMIT} \ + && git -C pto-isa checkout --detach ${PTO_ISA_COMMIT} \ + && test "$(git -C pto-isa rev-parse HEAD)" = "${PTO_ISA_COMMIT}" ENV PTO_LIB_PATH=/sources/pto-isa ENV BISHENG_OPTS="-I${PTO_LIB_PATH}/include/pto \ -fPIC -shared -O2 -std=c++17 \ diff --git a/docker/README.md b/docker/README.md index e638c71731..b47a9acf30 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,12 +1,15 @@ Build: +Run these commands from the PTOAS repository root so that every `COPY` source +is resolved against the reviewed checkout. + ```bash -docker build . -t ptoas:py3.11 +docker build -f docker/Dockerfile . -t ptoas:py3.11 # default to py3.11 to be compatible with readily-availble CANN images at # https://quay.io/repository/ascend/cann?tab=tags & https://github.com/Ascend/cann-container-image/tree/main/cann # optional, to change python version -docker build . -t ptoas:py3.12 --build-arg PY_VER=cp312-cp312 +docker build -f docker/Dockerfile . -t ptoas:py3.12 --build-arg PY_VER=cp312-cp312 ``` To test compiler: @@ -47,4 +50,4 @@ sudo docker run --rm -it --ipc=host --privileged \ -v /etc/ascend_install.info:/etc/ascend_install.info:ro \ -v $HOME:/mounted_home -w /mounted_home \ ptoas:py3.11 /bin/bash -``` \ No newline at end of file +``` diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index 3eb97315b1..ed49194750 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -373,6 +373,11 @@ static bool isVerifierTargetLinx(Operation *op) { static bool shouldBypassDecodedMemrefVerifier(Operation *op) { if (!op) return false; + // Decoded memrefs and bind_tile aliases are a legacy A2/A3/A5 + // representation. Linx must verify the effective operation explicitly; + // never use representation alone as an architecture-wide success path. + if (isVerifierTargetLinx(op)) + return false; for (Value operand : op->getOperands()) { if (isa(operand.getType())) return true; @@ -1602,8 +1607,30 @@ static std::optional getTileBufLogicalLayout(pto::TileBufType type) } static bool isRowMajorTileBuf(Type ty) { - auto tb = mlir::dyn_cast(ty); - return tb && tb.getBLayoutValueI32() == static_cast(pto::BLayout::RowMajor); + if (auto tb = mlir::dyn_cast(ty)) + return tb.getBLayoutValueI32() == + static_cast(pto::BLayout::RowMajor); + // Decoded ND tiles use row-major strides and may carry padding or a dynamic + // base offset while materialize-tile-handles introduces bind_tile aliases. + if (auto memref = mlir::dyn_cast(ty)) { + SmallVector strides; + int64_t offset = 0; + if (failed(memref.getStridesAndOffset(strides, offset)) || + strides.empty() || strides.back() != 1) + return false; + ArrayRef shape = memref.getShape(); + for (int64_t index = static_cast(strides.size()) - 2; index >= 0; + --index) { + int64_t innerExtent = shape[index + 1]; + if (ShapedType::isDynamic(strides[index]) || + ShapedType::isDynamic(strides[index + 1]) || + ShapedType::isDynamic(innerExtent) || + strides[index] < strides[index + 1] * innerExtent) + return false; + } + return true; + } + return false; } static LogicalResult verifyRowReductionSrcLayout(Operation *op, Type ty, @@ -2335,7 +2362,41 @@ LogicalResult TLoadOp::verify() { return success(); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + auto verifyLinx = [&]() -> LogicalResult { + Type srcTy = getSrc().getType(); + Type dstTy = getDst().getType(); + if (!isa(srcTy) || + !isa(dstTy)) + return emitOpError( + "expects Linx tload src to be a memref/partition tensor view and " + "dst to be a memref/tile buffer"); + if (failed(verifyTileBufCommon(*this, dstTy, "dst"))) + return failure(); + + auto srcSpace = getPTOMemorySpaceEnum(srcTy); + auto dstSpace = getPTOMemorySpaceEnum(dstTy); + if (!srcSpace || *srcSpace != pto::AddressSpace::GM) + return emitOpError("expects Linx tload src to use loc=gm"); + if (!dstSpace || *dstSpace != pto::AddressSpace::VEC) + return emitOpError("expects Linx tload dst to use loc=vec"); + if (getElemByteSize(getElemTy(srcTy)) != getElemByteSize(getElemTy(dstTy))) + return emitOpError( + "expects Linx tload src and dst element sizes to match"); + for (auto [name, shape] : + {std::pair>{"src", getShapeVec(srcTy)}, + std::pair>{ + "dst", getValidShapeVec(dstTy)}}) { + for (auto [index, dim] : llvm::enumerate(shape)) { + if (dim != ShapedType::kDynamic && dim <= 0) + return emitOpError() << "expects Linx tload " << name << " shape[" + << index << "] to be positive"; + } + } + return success(); + }; + + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5, + verifyLinx); } LogicalResult TPrefetchOp::verify() { @@ -5193,15 +5254,34 @@ mlir::LogicalResult mlir::pto::TInsertOp::verify() { "acc->mat, vec->mat, or vec->vec"); }; auto verifyLinx = [&]() -> LogicalResult { - auto common = verifyCommon(); - if (failed(common)) + Type srcTy = getSrc().getType(); + Type dstTy = getDst().getType(); + if (!isa(srcTy) || + !isa(dstTy)) + return emitOpError( + "expects Linx tinsert src/dst to be tile buffers or decoded memrefs"); + if (failed(verifyTileBufCommon(*this, srcTy, "src")) || + failed(verifyTileBufCommon(*this, dstTy, "dst")) || + failed(verifyNonNegativeIndexRowCol( + *getOperation(), getIndexRow(), getIndexCol(), + /*includeIndexAndIntOpsInConstFold=*/true)) || + failed(verifyInsertStaticBoundsCommon( + *getOperation(), getIndexRow(), getIndexCol(), srcTy, dstTy, + /*includeIndexAndIntOpsInConstFold=*/true))) return failure(); - auto [srcTy, dstTy, srcTb, dstTb, srcElem, dstElem, srcSpace, dstSpace] = - *common; + Type srcElem = getElemTy(srcTy); + Type dstElem = getElemTy(dstTy); + auto srcSpace = getPTOMemorySpaceEnum(srcTy); + auto dstSpace = getPTOMemorySpaceEnum(dstTy); if (!srcSpace || !dstSpace || *srcSpace != pto::AddressSpace::VEC || *dstSpace != pto::AddressSpace::VEC) return emitOpError("expects Linx VEC tinsert to use vec->vec"); - if (!isRowMajorNoneBoxND(srcTb) || !isRowMajorNoneBoxND(dstTb)) + auto isLinxND = [&](Type type) { + if (auto tile = dyn_cast(type)) + return isRowMajorNoneBoxND(tile); + return isRowMajorTileBuf(type); + }; + if (!isLinxND(srcTy) || !isLinxND(dstTy)) return emitOpError("expects Linx VEC tinsert src/dst to use ND layout"); if (srcElem != dstElem || !isA2A3VecInsertElemType(srcElem)) return emitOpError( diff --git a/test/lit/pto/v0581_linx_bind_tile_dispatch_fail_closed.pto b/test/lit/pto/v0581_linx_bind_tile_dispatch_fail_closed.pto new file mode 100644 index 0000000000..1c26f16d62 --- /dev/null +++ b/test/lit/pto/v0581_linx_bind_tile_dispatch_fail_closed.pto @@ -0,0 +1,17 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_bind_tile_dispatch_must_not_bypass() { + %c16 = arith.constant 16 : index + %storage = memref.alloc() : memref<16x16xf32, #pto.address_space> + %lhs = pto.bind_tile %storage, %c16, %c16 + {config = #pto.tile_buf_config} + : memref<16x16xf32, #pto.address_space> -> memref<16x16xf32, #pto.address_space> + %rhs = memref.alloc() : memref<16x16xf32, #pto.address_space> + %dst = memref.alloc() : memref<16x16xf32, #pto.address_space> + pto.tadd ins(%lhs, %rhs : memref<16x16xf32, #pto.address_space>, memref<16x16xf32, #pto.address_space>) outs(%dst : memref<16x16xf32, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tadd' op Linx legality is not implemented for this operation diff --git a/test/lit/pto/v0581_linx_direct_memref_verifier_fail_closed.pto b/test/lit/pto/v0581_linx_direct_memref_verifier_fail_closed.pto new file mode 100644 index 0000000000..82a9a57856 --- /dev/null +++ b/test/lit/pto/v0581_linx_direct_memref_verifier_fail_closed.pto @@ -0,0 +1,12 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_direct_memref_verifier_must_not_bypass() { + %src = memref.alloc() : memref<16x16xi32, #pto.address_space> + %dst = memref.alloc() : memref<16x16xi32, #pto.address_space> + pto.tabs ins(%src : memref<16x16xi32, #pto.address_space>) outs(%dst : memref<16x16xi32, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tabs' op expects element type to be f16 or f32 diff --git a/test/lit/pto/v0581_linx_memref_dispatch_fail_closed.pto b/test/lit/pto/v0581_linx_memref_dispatch_fail_closed.pto new file mode 100644 index 0000000000..ca94a3941c --- /dev/null +++ b/test/lit/pto/v0581_linx_memref_dispatch_fail_closed.pto @@ -0,0 +1,13 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_memref_dispatch_must_not_bypass() { + %lhs = memref.alloc() : memref<16x16xf32, #pto.address_space> + %rhs = memref.alloc() : memref<16x16xf32, #pto.address_space> + %dst = memref.alloc() : memref<16x16xf32, #pto.address_space> + pto.tadd ins(%lhs, %rhs : memref<16x16xf32, #pto.address_space>, memref<16x16xf32, #pto.address_space>) outs(%dst : memref<16x16xf32, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tadd' op Linx legality is not implemented for this operation diff --git a/test/lit/pto/v0581_linx_non_nd_memref_fail_closed.pto b/test/lit/pto/v0581_linx_non_nd_memref_fail_closed.pto new file mode 100644 index 0000000000..4c23fdb2b3 --- /dev/null +++ b/test/lit/pto/v0581_linx_non_nd_memref_fail_closed.pto @@ -0,0 +1,13 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_non_nd_memref_must_not_bypass() { + %lhs = memref.alloc() : memref<16x16xf32, strided<[1, 16]>, #pto.address_space> + %rhs = memref.alloc() : memref<16x16xf32, strided<[1, 16]>, #pto.address_space> + %dst = memref.alloc() : memref<16x16xf32, strided<[1, 16]>, #pto.address_space> + pto.tdiv ins(%lhs, %rhs : memref<16x16xf32, strided<[1, 16]>, #pto.address_space>, memref<16x16xf32, strided<[1, 16]>, #pto.address_space>) outs(%dst : memref<16x16xf32, strided<[1, 16]>, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tdiv' op expects src0, src1, and dst to use row-major layout diff --git a/test/lit/pto/v0581_linx_tload_decoded_contract.pto b/test/lit/pto/v0581_linx_tload_decoded_contract.pto new file mode 100644 index 0000000000..1f9b56d816 --- /dev/null +++ b/test/lit/pto/v0581_linx_tload_decoded_contract.pto @@ -0,0 +1,12 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_decoded_tload_rejects_non_vec_destination( + %src: memref<16x16xf32, #pto.address_space>) { + %dst = memref.alloc() : memref<16x16xf32, #pto.address_space> + pto.tload ins(%src : memref<16x16xf32, #pto.address_space>) outs(%dst : memref<16x16xf32, #pto.address_space>) {layout = #pto.layout} + return + } +} + +// CHECK: error: 'pto.tload' op expects Linx tload dst to use loc=vec diff --git a/tools/check_linx_verifier_contract.py b/tools/check_linx_verifier_contract.py new file mode 100644 index 0000000000..4db6eebb63 --- /dev/null +++ b/tools/check_linx_verifier_contract.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 +"""Check that decoded representations cannot bypass Linx verification.""" + +from __future__ import annotations + +from pathlib import Path + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + source = (root / "lib/PTO/IR/PTO.cpp").read_text() + helper_start = source.index("static bool shouldBypassDecodedMemrefVerifier") + helper_end = source.index("\n}\n", helper_start) + helper = source[helper_start:helper_end] + required = "if (isVerifierTargetLinx(op))\n return false;" + if required not in helper: + raise SystemExit( + "decoded memref/bind_tile verifier bypass must be disabled for Linx" + ) + print("Linx decoded-representation verifier contract OK") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py index 74066bb47b..ac2bc69838 100644 --- a/tools/check_release_delivery_contract.py +++ b/tools/check_release_delivery_contract.py @@ -4,9 +4,61 @@ from __future__ import annotations import argparse +import glob +import shlex from pathlib import Path +def local_copy_sources(dockerfile: str) -> list[tuple[int, str]]: + """Return local Docker COPY sources and reject unvalidated syntax.""" + sources: list[tuple[int, str]] = [] + for line_number, raw_line in enumerate(dockerfile.splitlines(), start=1): + line = raw_line.strip() + if not line.startswith("COPY "): + continue + tokens = shlex.split(line) + if tokens[0] != "COPY": + continue + index = 1 + from_stage = False + while index < len(tokens) and tokens[index].startswith("--"): + if tokens[index].startswith("--from="): + from_stage = True + index += 1 + if from_stage: + continue + operands = tokens[index:] + if len(operands) < 2: + raise SystemExit(f"Docker COPY on line {line_number} is malformed") + for source in operands[:-1]: + sources.append((line_number, source)) + return sources + + +def validate_local_copy_sources(root: Path, dockerfile: str) -> None: + root = root.resolve() + for line_number, source in local_copy_sources(dockerfile): + if source.startswith("/") or "$" in source: + raise SystemExit( + f"Docker COPY source on line {line_number} is not a fixed " + f"repository-root path: {source}" + ) + candidate = root / source + matches = glob.glob(str(candidate)) + if not matches: + raise SystemExit( + f"Docker COPY source on line {line_number} does not exist " + f"from repository-root context: {source}" + ) + for match in matches: + try: + Path(match).resolve().relative_to(root) + except ValueError as error: + raise SystemExit( + f"Docker COPY source escapes repository root: {source}" + ) from error + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--ptoas-root", type=Path, default=Path(__file__).resolve().parents[1]) @@ -14,10 +66,17 @@ def main() -> int: root = args.ptoas_root.resolve() dockerfile = (root / "docker/Dockerfile").read_text() + validate_local_copy_sources(root, dockerfile) if "COPY . ${PTO_SOURCE_DIR}" not in dockerfile: raise SystemExit("Docker build must COPY the reviewed PTOAS checkout") if "git clone https://github.com/zhangstevenunity/PTOAS.git" in dockerfile: raise SystemExit("Docker build must not clone an unpinned PTOAS fork") + if 'test "$(git -C pto-isa rev-parse HEAD)" = "${PTO_ISA_COMMIT}"' not in dockerfile: + raise SystemExit("Docker build must assert the exact PTO ISA checkout") + + readme = (root / "docker/README.md").read_text() + if "docker build -f docker/Dockerfile ." not in readme: + raise SystemExit("Docker README must document repository-root context") workflow = (root / ".github/workflows/isa_contract.yml").read_text() if "linxisa-v0.58.1" not in workflow: @@ -25,6 +84,16 @@ def main() -> int: exact_command = "python3 tools/check_v058_pto_manifest.py --ptoas-root ." if exact_command not in workflow: raise SystemExit("ISA tag workflow does not run the exact contract checker") + for required in ( + "docker/setup-buildx-action@", + "docker/build-push-action@", + "context: .", + "file: docker/Dockerfile", + "target: builder", + "push: false", + ): + if required not in workflow: + raise SystemExit(f"hosted Docker builder gate is missing: {required}") for name in ("build_wheel.yml", "build_wheel_mac.yml"): text = (root / ".github/workflows" / name).read_text() diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py new file mode 100644 index 0000000000..d7309cfe4e --- /dev/null +++ b/tools/test_release_delivery_contract.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Regression tests for the release delivery contract.""" + +from __future__ import annotations + +import re +import tempfile +import unittest +from pathlib import Path + +from check_release_delivery_contract import ( + local_copy_sources, + validate_local_copy_sources, +) + + +ROOT = Path(__file__).resolve().parents[1] + + +class ReleaseDeliveryContractTest(unittest.TestCase): + def test_copy_parser_checks_all_local_sources_and_skips_stage_copies(self) -> None: + dockerfile = "\n".join( + ( + "COPY first second /sources/", + "COPY --from=builder /artifact /output", + ) + ) + self.assertEqual(local_copy_sources(dockerfile), [(1, "first"), (1, "second")]) + + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + (root / "first").touch() + with self.assertRaisesRegex(SystemExit, "second"): + validate_local_copy_sources(root, dockerfile) + + def test_local_docker_copy_sources_exist_from_repository_root(self) -> None: + dockerfile = (ROOT / "docker/Dockerfile").read_text() + for match in re.finditer(r"^COPY\s+(?!--from=)(\S+)", dockerfile, re.MULTILINE): + source = match.group(1) + self.assertTrue( + (ROOT / source).exists(), + f"Docker COPY source is not valid from repository-root context: {source}", + ) + + def test_documented_build_uses_repository_root_context(self) -> None: + readme = (ROOT / "docker/README.md").read_text() + self.assertIn("docker build -f docker/Dockerfile .", readme) + + def test_hosted_builder_stage_gate_uses_buildkit(self) -> None: + workflow = (ROOT / ".github/workflows/isa_contract.yml").read_text() + self.assertIn("docker/setup-buildx-action@", workflow) + self.assertIn("docker/build-push-action@", workflow) + self.assertRegex(workflow, r"(?m)^\s+context:\s+\.\s*$") + self.assertRegex(workflow, r"(?m)^\s+file:\s+docker/Dockerfile\s*$") + self.assertRegex(workflow, r"(?m)^\s+target:\s+builder\s*$") + self.assertRegex(workflow, r"(?m)^\s+push:\s+false\s*$") + + +if __name__ == "__main__": + unittest.main() From 62564864716f2f97c0f475a0028f88b8c71d7c8d Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 01:36:59 +0800 Subject: [PATCH 15/32] fix: close Linx TSTORE_FP verifier bypass --- CMakeLists.txt | 4 + lib/PTO/IR/PTO.cpp | 23 +---- ...1_linx_tstore_fp_bind_tile_fail_closed.pto | 22 +++++ ...0581_linx_tstore_fp_memref_fail_closed.pto | 13 +++ tools/check_linx_verifier_contract.py | 93 +++++++++++++++++++ tools/test_check_linx_verifier_contract.py | 62 +++++++++++++ 6 files changed, 195 insertions(+), 22 deletions(-) create mode 100644 test/lit/pto/v0581_linx_tstore_fp_bind_tile_fail_closed.pto create mode 100644 test/lit/pto/v0581_linx_tstore_fp_memref_fail_closed.pto create mode 100644 tools/test_check_linx_verifier_contract.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 51fcfe107d..b12b3d7705 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -161,6 +161,10 @@ if(BUILD_TESTING) NAME linx_decoded_verifier_contract_check COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_linx_verifier_contract.py) + add_test( + NAME linx_decoded_verifier_contract_unit + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_check_linx_verifier_contract.py) add_test( NAME emitc_lvalue_contract_check COMMAND ${Python3_EXECUTABLE} diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index ed49194750..c6a90a4892 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -8989,17 +8989,7 @@ mlir::LogicalResult mlir::pto::TSqrtOp::verify() { return mlir::success(); } - - mlir::LogicalResult mlir::pto::TStoreFPOp::verify() { - auto shouldBypassDecoded = [&]() -> bool { - Value src = getSrc(); - Value fp = getFp(); - return isa(src.getType()) || isa(fp.getType()) || - src.getDefiningOp() || - fp.getDefiningOp(); - }; - auto verifyDstType = [&]() -> LogicalResult { Type dstTy = getDst().getType(); if (!isa(dstTy)) @@ -9068,20 +9058,9 @@ mlir::LogicalResult mlir::pto::TStoreFPOp::verify() { return emitOpError() << "expects src to be in the acc address space"; return mlir::success(); }; - if (shouldBypassDecoded()) - return success(); - switch (getVerifierTargetArch(getOperation())) { - case VerifierTargetArch::A2A3: - return verifyA2A3(); - case VerifierTargetArch::A5: - return verifyA5(); - case VerifierTargetArch::Linx: - return emitOpError("Linx legality is not implemented for this operation"); - } - return failure(); + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); } - mlir::LogicalResult mlir::pto::TSubOp::verify() { return verifyArithmeticBinaryTileOpWithArchDispatch( getOperation(), getSrc0().getType(), getSrc1().getType(), getDst().getType(), diff --git a/test/lit/pto/v0581_linx_tstore_fp_bind_tile_fail_closed.pto b/test/lit/pto/v0581_linx_tstore_fp_bind_tile_fail_closed.pto new file mode 100644 index 0000000000..9eb145ce61 --- /dev/null +++ b/test/lit/pto/v0581_linx_tstore_fp_bind_tile_fail_closed.pto @@ -0,0 +1,22 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_tstore_fp_bind_tile_must_not_bypass() { + %c1 = arith.constant 1 : index + %c16 = arith.constant 16 : index + %c32 = arith.constant 32 : index + %src_storage = memref.alloc() : memref<16x32xf32, #pto.address_space> + %src = pto.bind_tile %src_storage, %c16, %c32 + {config = #pto.tile_buf_config} + : memref<16x32xf32, #pto.address_space> -> memref<16x32xf32, #pto.address_space> + %fp_storage = memref.alloc() : memref<1x16xui64, #pto.address_space> + %fp = pto.bind_tile %fp_storage, %c1, %c16 + {config = #pto.tile_buf_config} + : memref<1x16xui64, #pto.address_space> -> memref<1x16xui64, #pto.address_space> + %dst = memref.alloc() : memref<32x32xi8, #pto.address_space> + pto.tstore_fp ins(%src, %fp : memref<16x32xf32, #pto.address_space>, memref<1x16xui64, #pto.address_space>) outs(%dst : memref<32x32xi8, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tstore_fp' op Linx legality is not implemented for this operation diff --git a/test/lit/pto/v0581_linx_tstore_fp_memref_fail_closed.pto b/test/lit/pto/v0581_linx_tstore_fp_memref_fail_closed.pto new file mode 100644 index 0000000000..a528dc5eca --- /dev/null +++ b/test/lit/pto/v0581_linx_tstore_fp_memref_fail_closed.pto @@ -0,0 +1,13 @@ +// RUN: not ptoas --pto-arch=linx %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "linx"} { + func.func @linx_tstore_fp_memref_must_not_bypass() { + %src = memref.alloc() : memref<16x32xf32, #pto.address_space> + %fp = memref.alloc() : memref<1x16xui64, #pto.address_space> + %dst = memref.alloc() : memref<32x32xi8, #pto.address_space> + pto.tstore_fp ins(%src, %fp : memref<16x32xf32, #pto.address_space>, memref<1x16xui64, #pto.address_space>) outs(%dst : memref<32x32xi8, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tstore_fp' op Linx legality is not implemented for this operation diff --git a/tools/check_linx_verifier_contract.py b/tools/check_linx_verifier_contract.py index 4db6eebb63..f25b5d43e4 100644 --- a/tools/check_linx_verifier_contract.py +++ b/tools/check_linx_verifier_contract.py @@ -3,9 +3,96 @@ from __future__ import annotations +import re from pathlib import Path +REPRESENTATION_MARKERS = ("MemRefType", "BindTileOp") + + +def _contains_representation_marker(text: str) -> bool: + return any(marker in text for marker in REPRESENTATION_MARKERS) + + +def _find_matching_open_paren(source: str, close_index: int) -> int | None: + depth = 0 + for index in range(close_index, -1, -1): + if source[index] == ")": + depth += 1 + elif source[index] == "(": + depth -= 1 + if depth == 0: + return index + return None + + +def _has_explicit_linx_fail_closed_path(source: str, position: int) -> bool: + function_start = source.rfind("LogicalResult", 0, position) + function_end = source.find("\n}\n", position) + if function_start < 0 or function_end < 0: + return False + function = source[function_start:function_end] + return ( + "VerifierTargetArch::Linx" in function + or "Linx legality is not implemented" in function + ) + + +def find_independent_early_success_bypasses(source: str) -> list[str]: + """Find operation-local representation predicates that return success.""" + findings: list[str] = [] + lambda_pattern = re.compile( + r"auto\s+(?P[A-Za-z_]\w*)\s*=\s*\[[^\]]*\]\s*" + r"\([^)]*\)\s*(?:->\s*bool\s*)?\{(?P.*?)\n\s*\};", + re.DOTALL, + ) + for match in lambda_pattern.finditer(source): + if not _contains_representation_marker(match.group("body")) or not ( + _has_explicit_linx_fail_closed_path(source, match.start()) + ): + continue + function_tail = source[match.end() :] + function_end = function_tail.find("\n}\n") + if function_end >= 0: + function_tail = function_tail[:function_end] + predicate = re.escape(match.group("name")) + early_success = re.compile( + rf"if\s*\(\s*{predicate}\s*\(\s*\)\s*\)\s*" + r"(?:\{\s*)?return\s+(?:mlir::)?success\(\);", + re.DOTALL, + ) + if early_success.search(function_tail): + findings.append(match.group("name")) + + success_pattern = re.compile(r"return\s+(?:mlir::)?success\(\);") + for match in success_pattern.finditer(source): + cursor = match.start() - 1 + while cursor >= 0 and source[cursor].isspace(): + cursor -= 1 + if cursor >= 0 and source[cursor] == "{": + cursor -= 1 + while cursor >= 0 and source[cursor].isspace(): + cursor -= 1 + if cursor < 0 or source[cursor] != ")": + continue + open_index = _find_matching_open_paren(source, cursor) + if open_index is None: + continue + keyword_end = open_index + keyword_start = keyword_end - 1 + while keyword_start >= 0 and source[keyword_start].isspace(): + keyword_start -= 1 + if source[max(0, keyword_start - 1) : keyword_start + 1] != "if": + continue + condition = source[open_index + 1 : cursor] + if _contains_representation_marker(condition) and ( + _has_explicit_linx_fail_closed_path(source, open_index) + ): + line = source.count("\n", 0, open_index) + 1 + findings.append(f"direct@{line}") + return findings + + def main() -> int: root = Path(__file__).resolve().parents[1] source = (root / "lib/PTO/IR/PTO.cpp").read_text() @@ -17,6 +104,12 @@ def main() -> int: raise SystemExit( "decoded memref/bind_tile verifier bypass must be disabled for Linx" ) + independent = find_independent_early_success_bypasses(source) + if independent: + raise SystemExit( + "independent representation-triggered early-success bypasses: " + + ", ".join(independent) + ) print("Linx decoded-representation verifier contract OK") return 0 diff --git a/tools/test_check_linx_verifier_contract.py b/tools/test_check_linx_verifier_contract.py new file mode 100644 index 0000000000..a42a1d1670 --- /dev/null +++ b/tools/test_check_linx_verifier_contract.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +"""Regression tests for the Linx decoded-representation verifier audit.""" + +from __future__ import annotations + +import unittest + +import check_linx_verifier_contract as contract + + +class LinxVerifierContractTest(unittest.TestCase): + def test_finds_independent_representation_early_success_lambda(self) -> None: + scanner = getattr(contract, "find_independent_early_success_bypasses", None) + self.assertIsNotNone(scanner, "checker must expose the independent bypass audit") + source = """ +LogicalResult pto::ExampleOp::verify() { + auto acceptDecoded = [&]() -> bool { + return isa(getSrc().getType()) || + getSrc().getDefiningOp(); + }; + if (acceptDecoded()) + return success(); + return emitOpError("Linx legality is not implemented"); +} +""" + self.assertEqual( + scanner(source), + ["acceptDecoded"], + ) + + def test_ignores_representation_predicate_without_early_success(self) -> None: + scanner = getattr(contract, "find_independent_early_success_bypasses", None) + self.assertIsNotNone(scanner, "checker must expose the independent bypass audit") + source = """ +LogicalResult pto::ExampleOp::verify() { + auto isDecoded = [&]() -> bool { + return isa(getSrc().getType()); + }; + if (isDecoded()) + return emitOpError("decoded form is unsupported"); + return success(); +} +""" + self.assertEqual(scanner(source), []) + + def test_finds_direct_representation_early_success(self) -> None: + scanner = getattr(contract, "find_independent_early_success_bypasses", None) + self.assertIsNotNone(scanner, "checker must expose the independent bypass audit") + source = """ +LogicalResult pto::ExampleOp::verify() { + if (isa(getSrc().getType())) + return success(); + return emitOpError("Linx legality is not implemented"); +} +""" + findings = scanner(source) + self.assertEqual(len(findings), 1) + self.assertRegex(findings[0], r"^direct@\d+$") + + +if __name__ == "__main__": + unittest.main() From 85b420d52635be7d1a88b130701730b71b24b387 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 01:44:12 +0800 Subject: [PATCH 16/32] fix: audit implicit Linx verifier dispatch --- tools/check_linx_verifier_contract.py | 41 ++++++++++++++++++++-- tools/test_check_linx_verifier_contract.py | 21 +++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/tools/check_linx_verifier_contract.py b/tools/check_linx_verifier_contract.py index f25b5d43e4..5695fc37a3 100644 --- a/tools/check_linx_verifier_contract.py +++ b/tools/check_linx_verifier_contract.py @@ -26,7 +26,41 @@ def _find_matching_open_paren(source: str, close_index: int) -> int | None: return None -def _has_explicit_linx_fail_closed_path(source: str, position: int) -> bool: +def _call_argument_count(source: str, open_index: int) -> int | None: + depth = 0 + commas = 0 + has_content = False + for character in source[open_index:]: + if character == "(": + depth += 1 + elif character == ")": + depth -= 1 + if depth == 0: + return commas + 1 if has_content else 0 + elif depth == 1: + if character == ",": + commas += 1 + elif not character.isspace(): + has_content = True + return None + + +def _has_implicit_linx_fail_closed_dispatch(function: str) -> bool: + dispatcher = "dispatchVerifierByArch" + position = 0 + while (position := function.find(dispatcher, position)) >= 0: + open_index = function.find("(", position + len(dispatcher)) + if open_index < 0: + return False + # The overload with two verifier callbacks (three total arguments) + # synthesizes the fail-closed Linx verifier. + if _call_argument_count(function, open_index) == 3: + return True + position = open_index + 1 + return False + + +def _has_linx_fail_closed_path(source: str, position: int) -> bool: function_start = source.rfind("LogicalResult", 0, position) function_end = source.find("\n}\n", position) if function_start < 0 or function_end < 0: @@ -35,6 +69,7 @@ def _has_explicit_linx_fail_closed_path(source: str, position: int) -> bool: return ( "VerifierTargetArch::Linx" in function or "Linx legality is not implemented" in function + or _has_implicit_linx_fail_closed_dispatch(function) ) @@ -48,7 +83,7 @@ def find_independent_early_success_bypasses(source: str) -> list[str]: ) for match in lambda_pattern.finditer(source): if not _contains_representation_marker(match.group("body")) or not ( - _has_explicit_linx_fail_closed_path(source, match.start()) + _has_linx_fail_closed_path(source, match.start()) ): continue function_tail = source[match.end() :] @@ -86,7 +121,7 @@ def find_independent_early_success_bypasses(source: str) -> list[str]: continue condition = source[open_index + 1 : cursor] if _contains_representation_marker(condition) and ( - _has_explicit_linx_fail_closed_path(source, open_index) + _has_linx_fail_closed_path(source, open_index) ): line = source.count("\n", 0, open_index) + 1 findings.append(f"direct@{line}") diff --git a/tools/test_check_linx_verifier_contract.py b/tools/test_check_linx_verifier_contract.py index a42a1d1670..c7c6eebf36 100644 --- a/tools/test_check_linx_verifier_contract.py +++ b/tools/test_check_linx_verifier_contract.py @@ -22,6 +22,27 @@ def test_finds_independent_representation_early_success_lambda(self) -> None: return success(); return emitOpError("Linx legality is not implemented"); } +""" + self.assertEqual( + scanner(source), + ["acceptDecoded"], + ) + + def test_finds_early_success_before_implicit_linx_fail_closed_dispatch( + self, + ) -> None: + scanner = getattr(contract, "find_independent_early_success_bypasses", None) + self.assertIsNotNone(scanner, "checker must expose the independent bypass audit") + source = """ +LogicalResult pto::TStoreFPOp::verify() { + auto acceptDecoded = [&]() -> bool { + return isa(getDst().getType()) || + getDst().getDefiningOp(); + }; + if (acceptDecoded()) + return success(); + return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); +} """ self.assertEqual( scanner(source), From b222ae04f07f3b2634341d9f601b62551ef350ed Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 01:51:16 +0800 Subject: [PATCH 17/32] chore: add required license headers --- .github/scripts/update_pto_isa_pin.py | 7 +++++++ tools/check_emitc_lvalue_contract.py | 8 ++++++++ tools/check_linx_verifier_contract.py | 8 ++++++++ tools/check_release_delivery_contract.py | 8 ++++++++ tools/test_check_linx_verifier_contract.py | 8 ++++++++ tools/test_release_delivery_contract.py | 8 ++++++++ 6 files changed, 47 insertions(+) diff --git a/.github/scripts/update_pto_isa_pin.py b/.github/scripts/update_pto_isa_pin.py index 49cf712651..ddac859b39 100755 --- a/.github/scripts/update_pto_isa_pin.py +++ b/.github/scripts/update_pto_isa_pin.py @@ -1,4 +1,11 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. import argparse import pathlib diff --git a/tools/check_emitc_lvalue_contract.py b/tools/check_emitc_lvalue_contract.py index 883df25667..36d1b6ddeb 100644 --- a/tools/check_emitc_lvalue_contract.py +++ b/tools/check_emitc_lvalue_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Guard the fail-closed boundary for void mutating EmitC calls.""" from pathlib import Path diff --git a/tools/check_linx_verifier_contract.py b/tools/check_linx_verifier_contract.py index 5695fc37a3..3c775ddfb1 100644 --- a/tools/check_linx_verifier_contract.py +++ b/tools/check_linx_verifier_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Check that decoded representations cannot bypass Linx verification.""" from __future__ import annotations diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py index ac2bc69838..11742bbf48 100644 --- a/tools/check_release_delivery_contract.py +++ b/tools/check_release_delivery_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Validate reproducible Docker source and ISA-tag workflow behavior.""" from __future__ import annotations diff --git a/tools/test_check_linx_verifier_contract.py b/tools/test_check_linx_verifier_contract.py index c7c6eebf36..09a32a7916 100644 --- a/tools/test_check_linx_verifier_contract.py +++ b/tools/test_check_linx_verifier_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Regression tests for the Linx decoded-representation verifier audit.""" from __future__ import annotations diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py index d7309cfe4e..6fd5daaaea 100644 --- a/tools/test_release_delivery_contract.py +++ b/tools/test_release_delivery_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Regression tests for the release delivery contract.""" from __future__ import annotations From 2dc3311727e7c44773beba47ca2ed33156479aa3 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 02:45:58 +0800 Subject: [PATCH 18/32] build: pin corrected LinxISA LLVM 0.58.1 head --- .github/workflows/build_wheel.yml | 2 +- .github/workflows/build_wheel_mac.yml | 2 +- .github/workflows/ci.yml | 2 +- README.md | 6 +++--- docker/Dockerfile | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 3fb2d2852b..2e40109337 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: b14f16a0cccf1a360a6a64aca8606d7780db3049 + LLVM_COMMIT: f708abb5b1559e4bbeb03209523dc2097f0ae8d6 jobs: build_wheel: diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index a95d6a0893..73244af52a 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: b14f16a0cccf1a360a6a64aca8606d7780db3049 + LLVM_COMMIT: f708abb5b1559e4bbeb03209523dc2097f0ae8d6 jobs: build_wheel: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0612e84329..f4678257fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: b14f16a0cccf1a360a6a64aca8606d7780db3049 + LLVM_COMMIT: f708abb5b1559e4bbeb03209523dc2097f0ae8d6 LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core diff --git a/README.md b/README.md index e8865e954f..e74981b61d 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **ptoas** (`ptoas`) 是一个基于 LinxISA 受控 **LLVM/MLIR** (ISA release `linxisa-v0.58.1`,LLVM commit -`b14f16a0cccf1a360a6a64aca8606d7780db3049`)构建的专用编译器工具链, +`f708abb5b1559e4bbeb03209523dc2097f0ae8d6`)构建的专用编译器工具链, 专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 作为连接上层 AI 框架与底层各类NPU/GPGPU/CPU硬件,`ptoas` 采用 **Out-of-Tree** 架构构建,提供了完整的 C++ 与 Python 接口,主要职责包括: @@ -41,7 +41,7 @@ PTOAS/ ## 3. 构建指南 (Build Instructions) ⚠️ **重要提示**:本项目严格依赖 LinxISA LLVM -`b14f16a0cccf1a360a6a64aca8606d7780db3049`。`linxisa-v0.58.1` +`f708abb5b1559e4bbeb03209523dc2097f0ae8d6`。`linxisa-v0.58.1` 是 ISA release tag,不是 PTOAS 产品版本;不要替换为同名上游 LLVM tag。 @@ -92,7 +92,7 @@ python3 -m pip install nanobind numpy cd $WORKSPACE_DIR git clone https://github.com/LinxISA/llvm-project.git cd $LLVM_SOURCE_DIR -git checkout --detach b14f16a0cccf1a360a6a64aca8606d7780db3049 +git checkout --detach f708abb5b1559e4bbeb03209523dc2097f0ae8d6 # 2. 配置 CMake (构建动态库并启用 Python 绑定) cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ diff --git a/docker/Dockerfile b/docker/Dockerfile index 8d165e15ef..94464e2d83 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ ARG ARCH # NOTE: change $PY_VER for different Python versions (3.8 - 3.14 available) ARG PY_VER=cp311-cp311 ARG LLVM_REPOSITORY=https://github.com/LinxISA/llvm-project.git -ARG LLVM_COMMIT=b14f16a0cccf1a360a6a64aca8606d7780db3049 +ARG LLVM_COMMIT=f708abb5b1559e4bbeb03209523dc2097f0ae8d6 ## -- usually no need to change below -- From 09ecdcb3a91a1b5d279b78f618de124c7ac4b8e7 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 03:01:11 +0800 Subject: [PATCH 19/32] build: repin final LinxISA LLVM 0.58.1 --- .github/workflows/build_wheel.yml | 2 +- .github/workflows/build_wheel_mac.yml | 2 +- .github/workflows/ci.yml | 2 +- README.md | 6 +++--- docker/Dockerfile | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index 2e40109337..d6e0a4497d 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: f708abb5b1559e4bbeb03209523dc2097f0ae8d6 + LLVM_COMMIT: 7cbc206194e3ecd5b259d5683307334fa49f58af jobs: build_wheel: diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index 73244af52a..f97a345945 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: f708abb5b1559e4bbeb03209523dc2097f0ae8d6 + LLVM_COMMIT: 7cbc206194e3ecd5b259d5683307334fa49f58af jobs: build_wheel: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4678257fe..6acc9a644d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: f708abb5b1559e4bbeb03209523dc2097f0ae8d6 + LLVM_COMMIT: 7cbc206194e3ecd5b259d5683307334fa49f58af LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core diff --git a/README.md b/README.md index e74981b61d..5d17bf1441 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **ptoas** (`ptoas`) 是一个基于 LinxISA 受控 **LLVM/MLIR** (ISA release `linxisa-v0.58.1`,LLVM commit -`f708abb5b1559e4bbeb03209523dc2097f0ae8d6`)构建的专用编译器工具链, +`7cbc206194e3ecd5b259d5683307334fa49f58af`)构建的专用编译器工具链, 专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 作为连接上层 AI 框架与底层各类NPU/GPGPU/CPU硬件,`ptoas` 采用 **Out-of-Tree** 架构构建,提供了完整的 C++ 与 Python 接口,主要职责包括: @@ -41,7 +41,7 @@ PTOAS/ ## 3. 构建指南 (Build Instructions) ⚠️ **重要提示**:本项目严格依赖 LinxISA LLVM -`f708abb5b1559e4bbeb03209523dc2097f0ae8d6`。`linxisa-v0.58.1` +`7cbc206194e3ecd5b259d5683307334fa49f58af`。`linxisa-v0.58.1` 是 ISA release tag,不是 PTOAS 产品版本;不要替换为同名上游 LLVM tag。 @@ -92,7 +92,7 @@ python3 -m pip install nanobind numpy cd $WORKSPACE_DIR git clone https://github.com/LinxISA/llvm-project.git cd $LLVM_SOURCE_DIR -git checkout --detach f708abb5b1559e4bbeb03209523dc2097f0ae8d6 +git checkout --detach 7cbc206194e3ecd5b259d5683307334fa49f58af # 2. 配置 CMake (构建动态库并启用 Python 绑定) cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ diff --git a/docker/Dockerfile b/docker/Dockerfile index 94464e2d83..495144ebfb 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ ARG ARCH # NOTE: change $PY_VER for different Python versions (3.8 - 3.14 available) ARG PY_VER=cp311-cp311 ARG LLVM_REPOSITORY=https://github.com/LinxISA/llvm-project.git -ARG LLVM_COMMIT=f708abb5b1559e4bbeb03209523dc2097f0ae8d6 +ARG LLVM_COMMIT=7cbc206194e3ecd5b259d5683307334fa49f58af ## -- usually no need to change below -- From 5922084c952773468387dcdeee8b727437b38cdc Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 03:57:09 +0800 Subject: [PATCH 20/32] build: pin canonical LinxISA LLVM v0.58.1 head --- .github/workflows/build_wheel.yml | 2 +- .github/workflows/build_wheel_mac.yml | 2 +- .github/workflows/ci.yml | 2 +- README.md | 6 +++--- docker/Dockerfile | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build_wheel.yml b/.github/workflows/build_wheel.yml index d6e0a4497d..42ee5aff9e 100644 --- a/.github/workflows/build_wheel.yml +++ b/.github/workflows/build_wheel.yml @@ -25,7 +25,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 7cbc206194e3ecd5b259d5683307334fa49f58af + LLVM_COMMIT: 1245c0f89aab24104ea935fa686a0ac5ad9ab2c9 jobs: build_wheel: diff --git a/.github/workflows/build_wheel_mac.yml b/.github/workflows/build_wheel_mac.yml index f97a345945..0cda2122cc 100644 --- a/.github/workflows/build_wheel_mac.yml +++ b/.github/workflows/build_wheel_mac.yml @@ -24,7 +24,7 @@ permissions: env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 7cbc206194e3ecd5b259d5683307334fa49f58af + LLVM_COMMIT: 1245c0f89aab24104ea935fa686a0ac5ad9ab2c9 jobs: build_wheel: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6acc9a644d..16f6246368 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,7 +94,7 @@ jobs: runs-on: ubuntu-22.04 env: LLVM_REPOSITORY: https://github.com/LinxISA/llvm-project.git - LLVM_COMMIT: 7cbc206194e3ecd5b259d5683307334fa49f58af + LLVM_COMMIT: 1245c0f89aab24104ea935fa686a0ac5ad9ab2c9 LLVM_DIR: ${{ github.workspace }}/llvm-project/llvm/build-shared PTO_INSTALL_DIR: ${{ github.workspace }}/install MLIR_PYTHONPATH: ${{ github.workspace }}/llvm-project/llvm/build-shared/tools/mlir/python_packages/mlir_core diff --git a/README.md b/README.md index 5d17bf1441..c1e82ad558 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ **ptoas** (`ptoas`) 是一个基于 LinxISA 受控 **LLVM/MLIR** (ISA release `linxisa-v0.58.1`,LLVM commit -`7cbc206194e3ecd5b259d5683307334fa49f58af`)构建的专用编译器工具链, +`1245c0f89aab24104ea935fa686a0ac5ad9ab2c9`)构建的专用编译器工具链, 专为 **PTO Bytecode** (Programming Tiling Operator Bytecode) 设计。 作为连接上层 AI 框架与底层各类NPU/GPGPU/CPU硬件,`ptoas` 采用 **Out-of-Tree** 架构构建,提供了完整的 C++ 与 Python 接口,主要职责包括: @@ -41,7 +41,7 @@ PTOAS/ ## 3. 构建指南 (Build Instructions) ⚠️ **重要提示**:本项目严格依赖 LinxISA LLVM -`7cbc206194e3ecd5b259d5683307334fa49f58af`。`linxisa-v0.58.1` +`1245c0f89aab24104ea935fa686a0ac5ad9ab2c9`。`linxisa-v0.58.1` 是 ISA release tag,不是 PTOAS 产品版本;不要替换为同名上游 LLVM tag。 @@ -92,7 +92,7 @@ python3 -m pip install nanobind numpy cd $WORKSPACE_DIR git clone https://github.com/LinxISA/llvm-project.git cd $LLVM_SOURCE_DIR -git checkout --detach 7cbc206194e3ecd5b259d5683307334fa49f58af +git checkout --detach 1245c0f89aab24104ea935fa686a0ac5ad9ab2c9 # 2. 配置 CMake (构建动态库并启用 Python 绑定) cmake -G Ninja -S llvm -B $LLVM_BUILD_DIR \ diff --git a/docker/Dockerfile b/docker/Dockerfile index 495144ebfb..f3919570ee 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -11,7 +11,7 @@ ARG ARCH # NOTE: change $PY_VER for different Python versions (3.8 - 3.14 available) ARG PY_VER=cp311-cp311 ARG LLVM_REPOSITORY=https://github.com/LinxISA/llvm-project.git -ARG LLVM_COMMIT=7cbc206194e3ecd5b259d5683307334fa49f58af +ARG LLVM_COMMIT=1245c0f89aab24104ea935fa686a0ac5ad9ab2c9 ## -- usually no need to change below -- From 27754c94ade551227e08b9ba804303f1e1d52959 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 05:49:43 +0800 Subject: [PATCH 21/32] fix: package nanobind runtime for Linux wheels --- CMakeLists.txt | 4 ++ lib/Bindings/Python/CMakeLists.txt | 7 +++ tools/check_python_binding_link_contract.py | 23 ++++++- ...test_check_python_binding_link_contract.py | 60 +++++++++++++++++++ 4 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 tools/test_check_python_binding_link_contract.py diff --git a/CMakeLists.txt b/CMakeLists.txt index b12b3d7705..e7bf6e3f52 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,6 +148,10 @@ if(BUILD_TESTING) COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/check_python_binding_link_contract.py --ptoas-root ${CMAKE_CURRENT_SOURCE_DIR}) + add_test( + NAME python_binding_link_contract_unit + COMMAND ${Python3_EXECUTABLE} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_check_python_binding_link_contract.py) add_test( NAME release_delivery_contract_check COMMAND ${Python3_EXECUTABLE} diff --git a/lib/Bindings/Python/CMakeLists.txt b/lib/Bindings/Python/CMakeLists.txt index e489ef2e4b..4e13f23a31 100644 --- a/lib/Bindings/Python/CMakeLists.txt +++ b/lib/Bindings/Python/CMakeLists.txt @@ -34,6 +34,13 @@ nanobind_add_module(_pto # hardening flags stay enabled. if(UNIX AND NOT APPLE) target_link_options(nanobind-mlir PRIVATE "LINKER:-z,undefs") + # NB_SHARED creates libnanobind-mlir.so outside LLVM's library directory. + # Install it into the directory exported to auditwheel so every architecture + # can resolve and vendor the runtime into the repaired wheel. + install(TARGETS nanobind-mlir + LIBRARY DESTINATION lib + COMPONENT PTOASPythonRuntime + ) endif() target_compile_definitions(_pto PRIVATE diff --git a/tools/check_python_binding_link_contract.py b/tools/check_python_binding_link_contract.py index 47dd7c0236..33c6d692ba 100644 --- a/tools/check_python_binding_link_contract.py +++ b/tools/check_python_binding_link_contract.py @@ -36,7 +36,28 @@ def main() -> int: "LINKER:-z,undefs so CPython resolves its ABI symbols at module load" ) - print("OK: Python binding ELF link contract is explicit") + install_contract = re.compile( + r"install\s*\(\s*TARGETS\s+nanobind-mlir\s+" + r"LIBRARY\s+DESTINATION\s+lib\s+" + r"(?:COMPONENT\s+PTOASPythonRuntime\s+)?\)", + re.DOTALL, + ) + if not install_contract.search(source): + raise SystemExit( + "error: install nanobind-mlir into lib so auditwheel can locate " + "the NB_SHARED runtime" + ) + + workflow_path = args.ptoas_root / ".github" / "workflows" / "build_wheel.yml" + workflow = workflow_path.read_text(encoding="utf-8") + if "$PTO_INSTALL_DIR/lib" not in workflow: + raise SystemExit( + "error: auditwheel LD_LIBRARY_PATH must include $PTO_INSTALL_DIR/lib" + ) + + print( + "OK: Python binding ELF link and shared-runtime packaging contract is explicit" + ) return 0 diff --git a/tools/test_check_python_binding_link_contract.py b/tools/test_check_python_binding_link_contract.py new file mode 100644 index 0000000000..31b7de888e --- /dev/null +++ b/tools/test_check_python_binding_link_contract.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Regression tests for the nanobind shared-runtime packaging contract.""" + +from __future__ import annotations + +import subprocess +import tempfile +import unittest +from pathlib import Path + + +CHECKER = Path(__file__).with_name("check_python_binding_link_contract.py") + + +class PythonBindingLinkContractTest(unittest.TestCase): + def run_checker(self, cmake_source: str, workflow: str) -> subprocess.CompletedProcess[str]: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + cmake_path = root / "lib/Bindings/Python/CMakeLists.txt" + cmake_path.parent.mkdir(parents=True) + cmake_path.write_text(cmake_source) + workflow_path = root / ".github/workflows/build_wheel.yml" + workflow_path.parent.mkdir(parents=True) + workflow_path.write_text(workflow) + return subprocess.run( + ["python3", str(CHECKER), "--ptoas-root", str(root)], + text=True, + capture_output=True, + check=False, + ) + + def test_rejects_shared_runtime_not_installed_in_auditwheel_search_path(self) -> None: + result = self.run_checker( + """ +if(UNIX AND NOT APPLE) + target_link_options(nanobind-mlir PRIVATE "LINKER:-z,undefs") +endif() +""", + "export LD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH\n", + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("install nanobind-mlir into lib", result.stderr) + + def test_accepts_installed_runtime_and_matching_auditwheel_search_path(self) -> None: + result = self.run_checker( + """ +if(UNIX AND NOT APPLE) + target_link_options(nanobind-mlir PRIVATE "LINKER:-z,undefs") + install(TARGETS nanobind-mlir + LIBRARY DESTINATION lib + ) +endif() +""", + "export LD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH\n", + ) + self.assertEqual(result.returncode, 0, result.stderr) + + +if __name__ == "__main__": + unittest.main() From 647106c23443c66f35d55a79cefd21a66788b1c6 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 05:51:47 +0800 Subject: [PATCH 22/32] chore: add wheel contract test license header --- tools/test_check_python_binding_link_contract.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/test_check_python_binding_link_contract.py b/tools/test_check_python_binding_link_contract.py index 31b7de888e..235ca4b5ce 100644 --- a/tools/test_check_python_binding_link_contract.py +++ b/tools/test_check_python_binding_link_contract.py @@ -1,4 +1,12 @@ #!/usr/bin/env python3 +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + """Regression tests for the nanobind shared-runtime packaging contract.""" from __future__ import annotations From d55a4be673f97c2d353db224e742d9b6c13fefd0 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 05:54:30 +0800 Subject: [PATCH 23/32] test: bind auditwheel lookup to repair step --- tools/check_python_binding_link_contract.py | 40 ++++++++++++- ...test_check_python_binding_link_contract.py | 58 ++++++++++++++++--- 2 files changed, 88 insertions(+), 10 deletions(-) diff --git a/tools/check_python_binding_link_contract.py b/tools/check_python_binding_link_contract.py index 33c6d692ba..75b27f8721 100644 --- a/tools/check_python_binding_link_contract.py +++ b/tools/check_python_binding_link_contract.py @@ -16,6 +16,20 @@ from pathlib import Path +def extract_workflow_step(workflow: str, name: str) -> str | None: + lines = workflow.splitlines() + marker = f"- name: {name}" + for index, line in enumerate(lines): + if line.strip() != marker: + continue + indent = line[: len(line) - len(line.lstrip())] + end = index + 1 + while end < len(lines) and not lines[end].startswith(f"{indent}- "): + end += 1 + return "\n".join(lines[index:end]) + return None + + def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("--ptoas-root", type=Path, default=Path.cwd()) @@ -50,9 +64,31 @@ def main() -> int: workflow_path = args.ptoas_root / ".github" / "workflows" / "build_wheel.yml" workflow = workflow_path.read_text(encoding="utf-8") - if "$PTO_INSTALL_DIR/lib" not in workflow: + repair_step = extract_workflow_step(workflow, "Repair wheel with auditwheel") + if repair_step is None: + raise SystemExit( + "error: workflow is missing the Repair wheel with auditwheel step" + ) + active_repair_lines = "\n".join( + line for line in repair_step.splitlines() if not line.lstrip().startswith("#") + ) + search_path = re.search( + r"(?m)^\s*export\s+LD_LIBRARY_PATH=(?P[^\n#]+)$", + active_repair_lines, + ) + if search_path is None or not re.search( + r"\$\{?PTO_INSTALL_DIR\}?/lib", search_path.group("value") + ): + raise SystemExit( + "error: Repair wheel with auditwheel must export LD_LIBRARY_PATH " + "including $PTO_INSTALL_DIR/lib" + ) + if not re.search( + r"(?m)^\s*auditwheel\s+repair(?:\s|$)", active_repair_lines + ): raise SystemExit( - "error: auditwheel LD_LIBRARY_PATH must include $PTO_INSTALL_DIR/lib" + "error: Repair wheel with auditwheel must invoke auditwheel repair " + "in the same step that exports LD_LIBRARY_PATH" ) print( diff --git a/tools/test_check_python_binding_link_contract.py b/tools/test_check_python_binding_link_contract.py index 235ca4b5ce..ae7220b9b2 100644 --- a/tools/test_check_python_binding_link_contract.py +++ b/tools/test_check_python_binding_link_contract.py @@ -18,10 +18,20 @@ CHECKER = Path(__file__).with_name("check_python_binding_link_contract.py") +VALID_PRODUCER = """ +if(UNIX AND NOT APPLE) + target_link_options(nanobind-mlir PRIVATE "LINKER:-z,undefs") + install(TARGETS nanobind-mlir + LIBRARY DESTINATION lib + ) +endif() +""" class PythonBindingLinkContractTest(unittest.TestCase): - def run_checker(self, cmake_source: str, workflow: str) -> subprocess.CompletedProcess[str]: + def run_checker( + self, cmake_source: str, workflow: str + ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as directory: root = Path(directory) cmake_path = root / "lib/Bindings/Python/CMakeLists.txt" @@ -51,18 +61,50 @@ def test_rejects_shared_runtime_not_installed_in_auditwheel_search_path(self) -> def test_accepts_installed_runtime_and_matching_auditwheel_search_path(self) -> None: result = self.run_checker( + VALID_PRODUCER, """ -if(UNIX AND NOT APPLE) - target_link_options(nanobind-mlir PRIVATE "LINKER:-z,undefs") - install(TARGETS nanobind-mlir - LIBRARY DESTINATION lib - ) -endif() +jobs: + build: + steps: + - name: Repair wheel with auditwheel + run: | + export LD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH + auditwheel repair dist/ptoas.whl -w wheelhouse """, - "export LD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH\n", ) self.assertEqual(result.returncode, 0, result.stderr) + def test_rejects_valid_producer_with_missing_repair_step_search_path(self) -> None: + result = self.run_checker( + VALID_PRODUCER, + """ +jobs: + build: + steps: + - name: Repair wheel with auditwheel + run: auditwheel repair dist/ptoas.whl -w wheelhouse +""", + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Repair wheel with auditwheel", result.stderr) + + def test_rejects_search_path_only_in_unrelated_comment_and_step(self) -> None: + result = self.run_checker( + VALID_PRODUCER, + """ +# $PTO_INSTALL_DIR/lib is documented here but not exported by repair. +jobs: + build: + steps: + - name: Diagnose installed libraries + run: export LD_LIBRARY_PATH=$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH + - name: Repair wheel with auditwheel + run: auditwheel repair dist/ptoas.whl -w wheelhouse +""", + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("Repair wheel with auditwheel", result.stderr) + if __name__ == "__main__": unittest.main() From 036212fbd55162326ce5fb75b93afbc3bbc73533 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 07:56:46 +0800 Subject: [PATCH 24/32] test: validate packaged PTO ISA identity --- docker/collect_ptoas_dist.sh | 4 ++-- docker/collect_ptoas_dist_mac.sh | 4 ++-- docker/test_ptoas_cli.sh | 4 ++-- tools/check_release_delivery_contract.py | 11 +++++++++++ tools/test_release_delivery_contract.py | 13 +++++++++++++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/docker/collect_ptoas_dist.sh b/docker/collect_ptoas_dist.sh index ca035cfc78..ade26a218f 100755 --- a/docker/collect_ptoas_dist.sh +++ b/docker/collect_ptoas_dist.sh @@ -164,13 +164,13 @@ VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" echo "$VERSION_OUTPUT" if [ -n "${PTOAS_VERSION:-}" ]; then - EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION}" + EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" if [ "${VERSION_OUTPUT}" != "${EXPECTED_VERSION_OUTPUT}" ]; then echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 exit 1 fi else - echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+$' + echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' fi # Show collected files diff --git a/docker/collect_ptoas_dist_mac.sh b/docker/collect_ptoas_dist_mac.sh index 91306349b2..e77b4da5ed 100644 --- a/docker/collect_ptoas_dist_mac.sh +++ b/docker/collect_ptoas_dist_mac.sh @@ -320,13 +320,13 @@ VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" echo "$VERSION_OUTPUT" if [ -n "${PTOAS_VERSION:-}" ]; then - EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION}" + EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" if [ "${VERSION_OUTPUT}" != "${EXPECTED_VERSION_OUTPUT}" ]; then echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 exit 1 fi else - echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+$' + echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' fi env -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" \ diff --git a/docker/test_ptoas_cli.sh b/docker/test_ptoas_cli.sh index 70951784f3..3f086a4ead 100755 --- a/docker/test_ptoas_cli.sh +++ b/docker/test_ptoas_cli.sh @@ -39,13 +39,13 @@ echo "Checking ptoas version..." VERSION_OUTPUT="$(ptoas --version | tr -d '\r')" echo "$VERSION_OUTPUT" if [ -n "${PTOAS_VERSION:-}" ]; then - EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION}" + EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" if [ "${VERSION_OUTPUT}" != "${EXPECTED_VERSION_OUTPUT}" ]; then echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 exit 1 fi else - echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+$' + echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' fi # Test MatMul sample diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py index 11742bbf48..7a20a32dd9 100644 --- a/tools/check_release_delivery_contract.py +++ b/tools/check_release_delivery_contract.py @@ -108,6 +108,17 @@ def main() -> int: if 'GITHUB_REF_NAME}" = "linxisa-v0.58.1"' in text: raise SystemExit(f"{name} conflates ISA identity with PTOAS product version") + expected_version = 'EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)"' + fallback_version = "grep -Eq '^ptoas [0-9]+\\.[0-9]+ \\(PTO ISA 0\\.58\\.1\\)$'" + for name in ( + "docker/test_ptoas_cli.sh", + "docker/collect_ptoas_dist.sh", + "docker/collect_ptoas_dist_mac.sh", + ): + text = (root / name).read_text() + if expected_version not in text or fallback_version not in text: + raise SystemExit(f"{name} does not verify the exact PTO ISA 0.58.1 CLI identity") + print("release delivery contract OK") return 0 diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py index 6fd5daaaea..f25d54c439 100644 --- a/tools/test_release_delivery_contract.py +++ b/tools/test_release_delivery_contract.py @@ -63,6 +63,19 @@ def test_hosted_builder_stage_gate_uses_buildkit(self) -> None: self.assertRegex(workflow, r"(?m)^\s+target:\s+builder\s*$") self.assertRegex(workflow, r"(?m)^\s+push:\s+false\s*$") + def test_packaged_cli_checks_exact_pto_isa_identity(self) -> None: + expected = 'EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)"' + fallback = "grep -Eq '^ptoas [0-9]+\\.[0-9]+ \\(PTO ISA 0\\.58\\.1\\)$'" + for relative in ( + "docker/test_ptoas_cli.sh", + "docker/collect_ptoas_dist.sh", + "docker/collect_ptoas_dist_mac.sh", + ): + with self.subTest(script=relative): + script = (ROOT / relative).read_text() + self.assertIn(expected, script) + self.assertIn(fallback, script) + if __name__ == "__main__": unittest.main() From 32c6b5441801ae6b6047872266c32f073134c148 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 08:04:03 +0800 Subject: [PATCH 25/32] fix: fail closed on packaged CLI identity --- docker/check_ptoas_cli_identity.sh | 37 ++++++++++++ docker/collect_ptoas_dist.sh | 10 +--- docker/collect_ptoas_dist_mac.sh | 10 +--- docker/test_ptoas_cli.sh | 10 +--- tools/check_release_delivery_contract.py | 71 +++++++++++++++++++++--- tools/test_release_delivery_contract.py | 39 +++++++++++-- 6 files changed, 138 insertions(+), 39 deletions(-) create mode 100644 docker/check_ptoas_cli_identity.sh diff --git a/docker/check_ptoas_cli_identity.sh b/docker/check_ptoas_cli_identity.sh new file mode 100644 index 0000000000..21bdc9353d --- /dev/null +++ b/docker/check_ptoas_cli_identity.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +# Copyright (c) 2026 Huawei Technologies Co., Ltd. +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of +# CANN Open Software License Agreement Version 2.0 (the "License"). +# Please refer to the License for details. You may not use this file except in compliance with the License. +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. +# See LICENSE in the root of the software repository for the full text of the License. + +set -euo pipefail + +if [[ $# -lt 1 || $# -gt 2 ]]; then + echo "Usage: $0 [product-version]" >&2 + exit 2 +fi + +VERSION_OUTPUT=$1 +PTOAS_VERSION=${2:-} + +if [[ "$VERSION_OUTPUT" == *$'\n'* || "$VERSION_OUTPUT" == *$'\r'* ]]; then + echo "Error: ptoas --version must emit exactly one line" >&2 + exit 1 +fi + +if [[ -n "$PTOAS_VERSION" ]]; then + EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" + if [[ "$VERSION_OUTPUT" != "$EXPECTED_VERSION_OUTPUT" ]]; then + echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 + exit 1 + fi +else + VERSION_PATTERN='^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' + if [[ ! "$VERSION_OUTPUT" =~ $VERSION_PATTERN ]]; then + echo "Error: invalid packaged ptoas identity '${VERSION_OUTPUT}'" >&2 + exit 1 + fi +fi diff --git a/docker/collect_ptoas_dist.sh b/docker/collect_ptoas_dist.sh index ade26a218f..5092848c08 100755 --- a/docker/collect_ptoas_dist.sh +++ b/docker/collect_ptoas_dist.sh @@ -163,15 +163,7 @@ echo "Smoke testing packaged ptoas dist..." VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" echo "$VERSION_OUTPUT" -if [ -n "${PTOAS_VERSION:-}" ]; then - EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" - if [ "${VERSION_OUTPUT}" != "${EXPECTED_VERSION_OUTPUT}" ]; then - echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 - exit 1 - fi -else - echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' -fi +bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" # Show collected files echo "" diff --git a/docker/collect_ptoas_dist_mac.sh b/docker/collect_ptoas_dist_mac.sh index e77b4da5ed..3c925d9ce8 100644 --- a/docker/collect_ptoas_dist_mac.sh +++ b/docker/collect_ptoas_dist_mac.sh @@ -319,15 +319,7 @@ echo "Smoke testing packaged ptoas dist..." VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" echo "$VERSION_OUTPUT" -if [ -n "${PTOAS_VERSION:-}" ]; then - EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" - if [ "${VERSION_OUTPUT}" != "${EXPECTED_VERSION_OUTPUT}" ]; then - echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 - exit 1 - fi -else - echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' -fi +bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" env -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" \ "${PTO_SOURCE_DIR}/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto" \ diff --git a/docker/test_ptoas_cli.sh b/docker/test_ptoas_cli.sh index 3f086a4ead..2e7dc7c981 100755 --- a/docker/test_ptoas_cli.sh +++ b/docker/test_ptoas_cli.sh @@ -38,15 +38,7 @@ which ptoas echo "Checking ptoas version..." VERSION_OUTPUT="$(ptoas --version | tr -d '\r')" echo "$VERSION_OUTPUT" -if [ -n "${PTOAS_VERSION:-}" ]; then - EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)" - if [ "${VERSION_OUTPUT}" != "${EXPECTED_VERSION_OUTPUT}" ]; then - echo "Error: expected '${EXPECTED_VERSION_OUTPUT}', got '${VERSION_OUTPUT}'" >&2 - exit 1 - fi -else - echo "$VERSION_OUTPUT" | grep -Eq '^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' -fi +bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" # Test MatMul sample echo "Testing MatMul sample..." diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py index 7a20a32dd9..4ead42624a 100644 --- a/tools/check_release_delivery_contract.py +++ b/tools/check_release_delivery_contract.py @@ -14,6 +14,7 @@ import argparse import glob import shlex +import subprocess from pathlib import Path @@ -67,9 +68,55 @@ def validate_local_copy_sources(root: Path, dockerfile: str) -> None: ) from error +def active_shell_lines(script: str) -> set[str]: + """Return executable, non-comment shell lines for structural checks.""" + return { + line.strip() + for line in script.splitlines() + if line.strip() and not line.lstrip().startswith("#") + } + + +def validate_cli_identity_helper(root: Path) -> None: + """Exercise the packaged CLI identity validator with positive and negative inputs.""" + helper = root / "docker/check_ptoas_cli_identity.sh" + + def run(output: str, product_version: str = "") -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["bash", str(helper), output, product_version], + check=False, + capture_output=True, + text=True, + ) + + for output, product_version in ( + ("ptoas 0.41 (PTO ISA 0.58.1)", "0.41"), + ("ptoas 0.41 (PTO ISA 0.58.1)", ""), + ): + result = run(output, product_version) + if result.returncode != 0: + raise SystemExit( + f"packaged CLI identity validator rejected valid input: {output}" + ) + + for output in ( + "ptoas 0.41", + "ptoas 0.40 (PTO ISA 0.58.1)", + "ptoas 0.41 (PTO ISA 0.58.0)", + "warning\nptoas 0.41 (PTO ISA 0.58.1)", + "ptoas 0.41 (PTO ISA 0.58.1)\nptoas 0.40", + ): + if run(output, "0.41").returncode == 0: + raise SystemExit( + f"packaged CLI identity validator accepted invalid input: {output!r}" + ) + + def main() -> int: parser = argparse.ArgumentParser() - parser.add_argument("--ptoas-root", type=Path, default=Path(__file__).resolve().parents[1]) + parser.add_argument( + "--ptoas-root", type=Path, default=Path(__file__).resolve().parents[1] + ) args = parser.parse_args() root = args.ptoas_root.resolve() @@ -79,7 +126,10 @@ def main() -> int: raise SystemExit("Docker build must COPY the reviewed PTOAS checkout") if "git clone https://github.com/zhangstevenunity/PTOAS.git" in dockerfile: raise SystemExit("Docker build must not clone an unpinned PTOAS fork") - if 'test "$(git -C pto-isa rev-parse HEAD)" = "${PTO_ISA_COMMIT}"' not in dockerfile: + if ( + 'test "$(git -C pto-isa rev-parse HEAD)" = "${PTO_ISA_COMMIT}"' + not in dockerfile + ): raise SystemExit("Docker build must assert the exact PTO ISA checkout") readme = (root / "docker/README.md").read_text() @@ -106,18 +156,25 @@ def main() -> int: for name in ("build_wheel.yml", "build_wheel_mac.yml"): text = (root / ".github/workflows" / name).read_text() if 'GITHUB_REF_NAME}" = "linxisa-v0.58.1"' in text: - raise SystemExit(f"{name} conflates ISA identity with PTOAS product version") + raise SystemExit( + f"{name} conflates ISA identity with PTOAS product version" + ) - expected_version = 'EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)"' - fallback_version = "grep -Eq '^ptoas [0-9]+\\.[0-9]+ \\(PTO ISA 0\\.58\\.1\\)$'" + validate_cli_identity_helper(root) + identity_invocation = ( + 'bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" ' + '"${VERSION_OUTPUT}" "${PTOAS_VERSION:-}"' + ) for name in ( "docker/test_ptoas_cli.sh", "docker/collect_ptoas_dist.sh", "docker/collect_ptoas_dist_mac.sh", ): text = (root / name).read_text() - if expected_version not in text or fallback_version not in text: - raise SystemExit(f"{name} does not verify the exact PTO ISA 0.58.1 CLI identity") + if identity_invocation not in active_shell_lines(text): + raise SystemExit( + f"{name} does not execute the exact PTO ISA 0.58.1 CLI identity validator" + ) print("release delivery contract OK") return 0 diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py index f25d54c439..a24c86ca87 100644 --- a/tools/test_release_delivery_contract.py +++ b/tools/test_release_delivery_contract.py @@ -12,11 +12,13 @@ from __future__ import annotations import re +import subprocess import tempfile import unittest from pathlib import Path from check_release_delivery_contract import ( + active_shell_lines, local_copy_sources, validate_local_copy_sources, ) @@ -63,9 +65,36 @@ def test_hosted_builder_stage_gate_uses_buildkit(self) -> None: self.assertRegex(workflow, r"(?m)^\s+target:\s+builder\s*$") self.assertRegex(workflow, r"(?m)^\s+push:\s+false\s*$") - def test_packaged_cli_checks_exact_pto_isa_identity(self) -> None: - expected = 'EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_VERSION} (PTO ISA 0.58.1)"' - fallback = "grep -Eq '^ptoas [0-9]+\\.[0-9]+ \\(PTO ISA 0\\.58\\.1\\)$'" + def test_packaged_cli_identity_validator_is_fail_closed(self) -> None: + helper = ROOT / "docker/check_ptoas_cli_identity.sh" + + def run( + output: str, product_version: str = "" + ) -> subprocess.CompletedProcess[str]: + return subprocess.run( + ["bash", str(helper), output, product_version], + check=False, + capture_output=True, + text=True, + ) + + self.assertEqual(run("ptoas 0.41 (PTO ISA 0.58.1)", "0.41").returncode, 0) + self.assertEqual(run("ptoas 0.41 (PTO ISA 0.58.1)").returncode, 0) + for invalid in ( + "ptoas 0.41", + "ptoas 0.40 (PTO ISA 0.58.1)", + "ptoas 0.41 (PTO ISA 0.58.0)", + "warning\nptoas 0.41 (PTO ISA 0.58.1)", + "ptoas 0.41 (PTO ISA 0.58.1)\nptoas 0.40", + ): + with self.subTest(output=invalid): + self.assertNotEqual(run(invalid, "0.41").returncode, 0) + + def test_packaging_scripts_execute_identity_validator(self) -> None: + invocation = ( + 'bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" ' + '"${VERSION_OUTPUT}" "${PTOAS_VERSION:-}"' + ) for relative in ( "docker/test_ptoas_cli.sh", "docker/collect_ptoas_dist.sh", @@ -73,8 +102,8 @@ def test_packaged_cli_checks_exact_pto_isa_identity(self) -> None: ): with self.subTest(script=relative): script = (ROOT / relative).read_text() - self.assertIn(expected, script) - self.assertIn(fallback, script) + self.assertIn(invocation, active_shell_lines(script)) + self.assertNotIn(invocation, active_shell_lines(f"# {invocation}")) if __name__ == "__main__": From 10f45575a1930c0d9319ccbbcf6a5138af6e9f2e Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 08:08:15 +0800 Subject: [PATCH 26/32] test: reject dead CLI identity guards --- docker/check_ptoas_cli_identity.sh | 5 +- tools/check_release_delivery_contract.py | 75 +++++++++++++++++++----- tools/test_release_delivery_contract.py | 38 +++++++++--- 3 files changed, 94 insertions(+), 24 deletions(-) diff --git a/docker/check_ptoas_cli_identity.sh b/docker/check_ptoas_cli_identity.sh index 21bdc9353d..ba7f7250b2 100644 --- a/docker/check_ptoas_cli_identity.sh +++ b/docker/check_ptoas_cli_identity.sh @@ -16,6 +16,7 @@ fi VERSION_OUTPUT=$1 PTOAS_VERSION=${2:-} +PTOAS_RELEASE_VERSION=0.41 if [[ "$VERSION_OUTPUT" == *$'\n'* || "$VERSION_OUTPUT" == *$'\r'* ]]; then echo "Error: ptoas --version must emit exactly one line" >&2 @@ -29,8 +30,8 @@ if [[ -n "$PTOAS_VERSION" ]]; then exit 1 fi else - VERSION_PATTERN='^ptoas [0-9]+\.[0-9]+ \(PTO ISA 0\.58\.1\)$' - if [[ ! "$VERSION_OUTPUT" =~ $VERSION_PATTERN ]]; then + EXPECTED_VERSION_OUTPUT="ptoas ${PTOAS_RELEASE_VERSION} (PTO ISA 0.58.1)" + if [[ "$VERSION_OUTPUT" != "$EXPECTED_VERSION_OUTPUT" ]]; then echo "Error: invalid packaged ptoas identity '${VERSION_OUTPUT}'" >&2 exit 1 fi diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py index 4ead42624a..7705a0b743 100644 --- a/tools/check_release_delivery_contract.py +++ b/tools/check_release_delivery_contract.py @@ -13,6 +13,7 @@ import argparse import glob +import re import shlex import subprocess from pathlib import Path @@ -68,13 +69,53 @@ def validate_local_copy_sources(root: Path, dockerfile: str) -> None: ) from error -def active_shell_lines(script: str) -> set[str]: - """Return executable, non-comment shell lines for structural checks.""" - return { - line.strip() - for line in script.splitlines() - if line.strip() and not line.lstrip().startswith("#") - } +def top_level_shell_lines(script: str) -> list[str]: + """Return executable top-level shell lines, excluding heredoc bodies.""" + result: list[str] = [] + depth = 0 + heredoc_delimiter: str | None = None + heredoc_pattern = re.compile(r"<<-?\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\1") + function_pattern = re.compile( + r"^(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\)\s*\{\s*$" + ) + + for raw_line in script.splitlines(): + stripped = raw_line.strip() + if heredoc_delimiter is not None: + if stripped == heredoc_delimiter: + heredoc_delimiter = None + continue + if not stripped or stripped.startswith("#"): + continue + + if depth == 0: + result.append(stripped) + + heredoc_match = heredoc_pattern.search(stripped) + if heredoc_match: + heredoc_delimiter = heredoc_match.group(2) + + if re.match(r"^(fi|done|esac)\b", stripped) or stripped.startswith("}"): + depth = max(0, depth - 1) + if ( + re.match(r"^(if|for|while|until|select)\b", stripped) + or re.match(r"^case\b.*\bin\s*$", stripped) + or function_pattern.match(stripped) + ): + depth += 1 + + return result + + +def has_top_level_identity_invocation(script: str, invocation: str) -> bool: + """Require the identity validator immediately after version output at top level.""" + lines = top_level_shell_lines(script) + return any( + line == invocation + and index > 0 + and lines[index - 1] == 'echo "$VERSION_OUTPUT"' + for index, line in enumerate(lines) + ) def validate_cli_identity_helper(root: Path) -> None: @@ -99,17 +140,20 @@ def run(output: str, product_version: str = "") -> subprocess.CompletedProcess[s f"packaged CLI identity validator rejected valid input: {output}" ) - for output in ( + invalid_outputs = ( "ptoas 0.41", "ptoas 0.40 (PTO ISA 0.58.1)", "ptoas 0.41 (PTO ISA 0.58.0)", "warning\nptoas 0.41 (PTO ISA 0.58.1)", "ptoas 0.41 (PTO ISA 0.58.1)\nptoas 0.40", - ): - if run(output, "0.41").returncode == 0: - raise SystemExit( - f"packaged CLI identity validator accepted invalid input: {output!r}" - ) + ) + for product_version in ("0.41", ""): + for output in invalid_outputs: + if run(output, product_version).returncode == 0: + raise SystemExit( + "packaged CLI identity validator accepted invalid input " + f"in product mode {product_version!r}: {output!r}" + ) def main() -> int: @@ -171,9 +215,10 @@ def main() -> int: "docker/collect_ptoas_dist_mac.sh", ): text = (root / name).read_text() - if identity_invocation not in active_shell_lines(text): + if not has_top_level_identity_invocation(text, identity_invocation): raise SystemExit( - f"{name} does not execute the exact PTO ISA 0.58.1 CLI identity validator" + f"{name} does not execute the exact PTO ISA 0.58.1 CLI identity " + "validator immediately after displaying the version" ) print("release delivery contract OK") diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py index a24c86ca87..9e279539ed 100644 --- a/tools/test_release_delivery_contract.py +++ b/tools/test_release_delivery_contract.py @@ -18,8 +18,9 @@ from pathlib import Path from check_release_delivery_contract import ( - active_shell_lines, + has_top_level_identity_invocation, local_copy_sources, + top_level_shell_lines, validate_local_copy_sources, ) @@ -80,15 +81,17 @@ def run( self.assertEqual(run("ptoas 0.41 (PTO ISA 0.58.1)", "0.41").returncode, 0) self.assertEqual(run("ptoas 0.41 (PTO ISA 0.58.1)").returncode, 0) - for invalid in ( + invalid_outputs = ( "ptoas 0.41", "ptoas 0.40 (PTO ISA 0.58.1)", "ptoas 0.41 (PTO ISA 0.58.0)", "warning\nptoas 0.41 (PTO ISA 0.58.1)", "ptoas 0.41 (PTO ISA 0.58.1)\nptoas 0.40", - ): - with self.subTest(output=invalid): - self.assertNotEqual(run(invalid, "0.41").returncode, 0) + ) + for product_version in ("0.41", ""): + for invalid in invalid_outputs: + with self.subTest(output=invalid, product_version=product_version): + self.assertNotEqual(run(invalid, product_version).returncode, 0) def test_packaging_scripts_execute_identity_validator(self) -> None: invocation = ( @@ -102,8 +105,29 @@ def test_packaging_scripts_execute_identity_validator(self) -> None: ): with self.subTest(script=relative): script = (ROOT / relative).read_text() - self.assertIn(invocation, active_shell_lines(script)) - self.assertNotIn(invocation, active_shell_lines(f"# {invocation}")) + self.assertTrue(has_top_level_identity_invocation(script, invocation)) + + dead_branch = "\n".join( + ('echo "$VERSION_OUTPUT"', "if false; then", invocation, "fi") + ) + heredoc = "\n".join( + ('echo "$VERSION_OUTPUT"', "cat <<'DEAD'", invocation, "DEAD") + ) + dead_function = "\n".join( + ( + 'echo "$VERSION_OUTPUT"', + "dead_check() {", + invocation, + "}", + ) + ) + for script in (f"# {invocation}", dead_branch, heredoc, dead_function): + with self.subTest(script=script): + self.assertFalse(has_top_level_identity_invocation(script, invocation)) + + def test_top_level_shell_parser_preserves_live_lines_after_heredocs(self) -> None: + script = "\n".join(("cat <<'PY'", "if false; then", "PY", "echo live")) + self.assertEqual(top_level_shell_lines(script), ["cat <<'PY'", "echo live"]) if __name__ == "__main__": From 6dd61468fbd428c774d7642b6499cbe487bd07c1 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 09:12:17 +0800 Subject: [PATCH 27/32] test: execute packaged identity guards --- docker/collect_ptoas_dist.sh | 11 ++- docker/collect_ptoas_dist_mac.sh | 11 ++- docker/test_ptoas_cli.sh | 11 ++- tools/check_release_delivery_contract.py | 98 ++++++------------------ tools/test_release_delivery_contract.py | 95 +++++++++++------------ 5 files changed, 99 insertions(+), 127 deletions(-) diff --git a/docker/collect_ptoas_dist.sh b/docker/collect_ptoas_dist.sh index 5092848c08..c2b552d51f 100755 --- a/docker/collect_ptoas_dist.sh +++ b/docker/collect_ptoas_dist.sh @@ -24,6 +24,15 @@ set -euo pipefail +if [[ "${1:-}" == "--check-ptoas-cli-identity" ]]; then + if [[ $# -lt 2 || $# -gt 3 ]]; then + echo "Usage: $0 --check-ptoas-cli-identity [product-version]" >&2 + exit 2 + fi + exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check_ptoas_cli_identity.sh" \ + "$2" "${3:-}" +fi + if [ $# -lt 1 ]; then echo "Usage: $0 " >&2 exit 1 @@ -163,7 +172,7 @@ echo "Smoke testing packaged ptoas dist..." VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" echo "$VERSION_OUTPUT" -bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +bash "$0" --check-ptoas-cli-identity "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" # Show collected files echo "" diff --git a/docker/collect_ptoas_dist_mac.sh b/docker/collect_ptoas_dist_mac.sh index 3c925d9ce8..f16a4be2d1 100644 --- a/docker/collect_ptoas_dist_mac.sh +++ b/docker/collect_ptoas_dist_mac.sh @@ -24,6 +24,15 @@ set -euo pipefail +if [[ "${1:-}" == "--check-ptoas-cli-identity" ]]; then + if [[ $# -lt 2 || $# -gt 3 ]]; then + echo "Usage: $0 --check-ptoas-cli-identity [product-version]" >&2 + exit 2 + fi + exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check_ptoas_cli_identity.sh" \ + "$2" "${3:-}" +fi + if [ $# -lt 1 ]; then echo "Usage: $0 " >&2 exit 1 @@ -319,7 +328,7 @@ echo "Smoke testing packaged ptoas dist..." VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" echo "$VERSION_OUTPUT" -bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +bash "$0" --check-ptoas-cli-identity "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" env -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" \ "${PTO_SOURCE_DIR}/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto" \ diff --git a/docker/test_ptoas_cli.sh b/docker/test_ptoas_cli.sh index 2e7dc7c981..cf4b9700b7 100755 --- a/docker/test_ptoas_cli.sh +++ b/docker/test_ptoas_cli.sh @@ -18,6 +18,15 @@ set -e +if [[ "${1:-}" == "--check-ptoas-cli-identity" ]]; then + if [[ $# -lt 2 || $# -gt 3 ]]; then + echo "Usage: $0 --check-ptoas-cli-identity [product-version]" >&2 + exit 2 + fi + exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check_ptoas_cli_identity.sh" \ + "$2" "${3:-}" +fi + # Validate required environment variables for var in PTO_SOURCE_DIR LLVM_BUILD_DIR PTO_INSTALL_DIR; do if [ -z "${!var}" ]; then @@ -38,7 +47,7 @@ which ptoas echo "Checking ptoas version..." VERSION_OUTPUT="$(ptoas --version | tr -d '\r')" echo "$VERSION_OUTPUT" -bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +bash "$0" --check-ptoas-cli-identity "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" # Test MatMul sample echo "Testing MatMul sample..." diff --git a/tools/check_release_delivery_contract.py b/tools/check_release_delivery_contract.py index 7705a0b743..2b503a15fa 100644 --- a/tools/check_release_delivery_contract.py +++ b/tools/check_release_delivery_contract.py @@ -13,7 +13,6 @@ import argparse import glob -import re import shlex import subprocess from pathlib import Path @@ -69,75 +68,35 @@ def validate_local_copy_sources(root: Path, dockerfile: str) -> None: ) from error -def top_level_shell_lines(script: str) -> list[str]: - """Return executable top-level shell lines, excluding heredoc bodies.""" - result: list[str] = [] - depth = 0 - heredoc_delimiter: str | None = None - heredoc_pattern = re.compile(r"<<-?\s*(['\"]?)([A-Za-z_][A-Za-z0-9_]*)\1") - function_pattern = re.compile( - r"^(?:function\s+)?[A-Za-z_][A-Za-z0-9_]*\s*\(\)\s*\{\s*$" - ) - - for raw_line in script.splitlines(): - stripped = raw_line.strip() - if heredoc_delimiter is not None: - if stripped == heredoc_delimiter: - heredoc_delimiter = None - continue - if not stripped or stripped.startswith("#"): - continue - - if depth == 0: - result.append(stripped) - - heredoc_match = heredoc_pattern.search(stripped) - if heredoc_match: - heredoc_delimiter = heredoc_match.group(2) - - if re.match(r"^(fi|done|esac)\b", stripped) or stripped.startswith("}"): - depth = max(0, depth - 1) - if ( - re.match(r"^(if|for|while|until|select)\b", stripped) - or re.match(r"^case\b.*\bin\s*$", stripped) - or function_pattern.match(stripped) - ): - depth += 1 - - return result - - -def has_top_level_identity_invocation(script: str, invocation: str) -> bool: - """Require the identity validator immediately after version output at top level.""" - lines = top_level_shell_lines(script) - return any( - line == invocation - and index > 0 - and lines[index - 1] == 'echo "$VERSION_OUTPUT"' - for index, line in enumerate(lines) +def run_packaging_identity_mode( + script: Path, output: str, product_version: str = "" +) -> subprocess.CompletedProcess[str]: + """Run one packaging script's executable identity-only path.""" + return subprocess.run( + [ + "bash", + str(script), + "--check-ptoas-cli-identity", + output, + product_version, + ], + check=False, + capture_output=True, + text=True, ) -def validate_cli_identity_helper(root: Path) -> None: - """Exercise the packaged CLI identity validator with positive and negative inputs.""" - helper = root / "docker/check_ptoas_cli_identity.sh" - - def run(output: str, product_version: str = "") -> subprocess.CompletedProcess[str]: - return subprocess.run( - ["bash", str(helper), output, product_version], - check=False, - capture_output=True, - text=True, - ) +def validate_packaging_identity_mode(script: Path) -> None: + """Exercise a packaging script with valid and adversarial identity outputs.""" for output, product_version in ( ("ptoas 0.41 (PTO ISA 0.58.1)", "0.41"), ("ptoas 0.41 (PTO ISA 0.58.1)", ""), ): - result = run(output, product_version) + result = run_packaging_identity_mode(script, output, product_version) if result.returncode != 0: raise SystemExit( - f"packaged CLI identity validator rejected valid input: {output}" + f"{script.name} rejected valid packaged CLI identity: {output}" ) invalid_outputs = ( @@ -149,9 +108,12 @@ def run(output: str, product_version: str = "") -> subprocess.CompletedProcess[s ) for product_version in ("0.41", ""): for output in invalid_outputs: - if run(output, product_version).returncode == 0: + if ( + run_packaging_identity_mode(script, output, product_version).returncode + == 0 + ): raise SystemExit( - "packaged CLI identity validator accepted invalid input " + f"{script.name} accepted invalid packaged CLI identity " f"in product mode {product_version!r}: {output!r}" ) @@ -204,22 +166,12 @@ def main() -> int: f"{name} conflates ISA identity with PTOAS product version" ) - validate_cli_identity_helper(root) - identity_invocation = ( - 'bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" ' - '"${VERSION_OUTPUT}" "${PTOAS_VERSION:-}"' - ) for name in ( "docker/test_ptoas_cli.sh", "docker/collect_ptoas_dist.sh", "docker/collect_ptoas_dist_mac.sh", ): - text = (root / name).read_text() - if not has_top_level_identity_invocation(text, identity_invocation): - raise SystemExit( - f"{name} does not execute the exact PTO ISA 0.58.1 CLI identity " - "validator immediately after displaying the version" - ) + validate_packaging_identity_mode(root / name) print("release delivery contract OK") return 0 diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py index 9e279539ed..0e4aeab68f 100644 --- a/tools/test_release_delivery_contract.py +++ b/tools/test_release_delivery_contract.py @@ -18,10 +18,10 @@ from pathlib import Path from check_release_delivery_contract import ( - has_top_level_identity_invocation, local_copy_sources, - top_level_shell_lines, + run_packaging_identity_mode, validate_local_copy_sources, + validate_packaging_identity_mode, ) @@ -67,20 +67,17 @@ def test_hosted_builder_stage_gate_uses_buildkit(self) -> None: self.assertRegex(workflow, r"(?m)^\s+push:\s+false\s*$") def test_packaged_cli_identity_validator_is_fail_closed(self) -> None: - helper = ROOT / "docker/check_ptoas_cli_identity.sh" + scripts = ( + ROOT / "docker/test_ptoas_cli.sh", + ROOT / "docker/collect_ptoas_dist.sh", + ROOT / "docker/collect_ptoas_dist_mac.sh", + ) def run( - output: str, product_version: str = "" + script: Path, output: str, product_version: str = "" ) -> subprocess.CompletedProcess[str]: - return subprocess.run( - ["bash", str(helper), output, product_version], - check=False, - capture_output=True, - text=True, - ) + return run_packaging_identity_mode(script, output, product_version) - self.assertEqual(run("ptoas 0.41 (PTO ISA 0.58.1)", "0.41").returncode, 0) - self.assertEqual(run("ptoas 0.41 (PTO ISA 0.58.1)").returncode, 0) invalid_outputs = ( "ptoas 0.41", "ptoas 0.40 (PTO ISA 0.58.1)", @@ -88,46 +85,42 @@ def run( "warning\nptoas 0.41 (PTO ISA 0.58.1)", "ptoas 0.41 (PTO ISA 0.58.1)\nptoas 0.40", ) - for product_version in ("0.41", ""): - for invalid in invalid_outputs: - with self.subTest(output=invalid, product_version=product_version): - self.assertNotEqual(run(invalid, product_version).returncode, 0) - - def test_packaging_scripts_execute_identity_validator(self) -> None: - invocation = ( - 'bash "${PTO_SOURCE_DIR}/docker/check_ptoas_cli_identity.sh" ' - '"${VERSION_OUTPUT}" "${PTOAS_VERSION:-}"' - ) - for relative in ( - "docker/test_ptoas_cli.sh", - "docker/collect_ptoas_dist.sh", - "docker/collect_ptoas_dist_mac.sh", - ): - with self.subTest(script=relative): - script = (ROOT / relative).read_text() - self.assertTrue(has_top_level_identity_invocation(script, invocation)) - - dead_branch = "\n".join( - ('echo "$VERSION_OUTPUT"', "if false; then", invocation, "fi") + for script in scripts: + with self.subTest( + script=script.name, output="valid", product_version="0.41" + ): + self.assertEqual( + run(script, "ptoas 0.41 (PTO ISA 0.58.1)", "0.41").returncode, + 0, + ) + with self.subTest(script=script.name, output="valid", product_version=""): + self.assertEqual( + run(script, "ptoas 0.41 (PTO ISA 0.58.1)").returncode, 0 + ) + for product_version in ("0.41", ""): + for invalid in invalid_outputs: + with self.subTest( + script=script.name, + output=invalid, + product_version=product_version, + ): + self.assertNotEqual( + run(script, invalid, product_version).returncode, 0 + ) + + def test_dead_identity_entrypoints_fail_dynamic_validation(self) -> None: + dead_scripts = ( + "function dead_check { exit 0; }; exit 1", + "false && { exit 0; }; exit 1", + "cat <<'DEAD-TEXT'\nexit 0\nDEAD-TEXT\nexit 1", + "cat <<\\DEAD\nexit 0\nDEAD\nexit 1", ) - heredoc = "\n".join( - ('echo "$VERSION_OUTPUT"', "cat <<'DEAD'", invocation, "DEAD") - ) - dead_function = "\n".join( - ( - 'echo "$VERSION_OUTPUT"', - "dead_check() {", - invocation, - "}", - ) - ) - for script in (f"# {invocation}", dead_branch, heredoc, dead_function): - with self.subTest(script=script): - self.assertFalse(has_top_level_identity_invocation(script, invocation)) - - def test_top_level_shell_parser_preserves_live_lines_after_heredocs(self) -> None: - script = "\n".join(("cat <<'PY'", "if false; then", "PY", "echo live")) - self.assertEqual(top_level_shell_lines(script), ["cat <<'PY'", "echo live"]) + with tempfile.TemporaryDirectory() as directory: + for index, body in enumerate(dead_scripts): + script = Path(directory) / f"dead-{index}.sh" + script.write_text(f"#!/usr/bin/env bash\n{body}\n") + with self.subTest(body=body), self.assertRaises(SystemExit): + validate_packaging_identity_mode(script) if __name__ == "__main__": From e7fedbe882713b62208d49f9e72a72eafeeb2a6d Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 09:27:49 +0800 Subject: [PATCH 28/32] test: join packaged identity control flow --- docker/collect_ptoas_dist.sh | 16 +++++++++---- docker/collect_ptoas_dist_mac.sh | 16 +++++++++---- docker/test_ptoas_cli.sh | 16 +++++++++---- tools/test_release_delivery_contract.py | 30 +++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 12 deletions(-) diff --git a/docker/collect_ptoas_dist.sh b/docker/collect_ptoas_dist.sh index c2b552d51f..8ac74ae2d4 100755 --- a/docker/collect_ptoas_dist.sh +++ b/docker/collect_ptoas_dist.sh @@ -24,14 +24,17 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IDENTITY_CHECK_ONLY=0 if [[ "${1:-}" == "--check-ptoas-cli-identity" ]]; then if [[ $# -lt 2 || $# -gt 3 ]]; then echo "Usage: $0 --check-ptoas-cli-identity [product-version]" >&2 exit 2 fi - exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check_ptoas_cli_identity.sh" \ - "$2" "${3:-}" -fi + IDENTITY_CHECK_ONLY=1 + VERSION_OUTPUT=$2 + PTOAS_VERSION=${3:-} +else if [ $# -lt 1 ]; then echo "Usage: $0 " >&2 @@ -171,8 +174,13 @@ chmod +x "${PTOAS_DIST_DIR}/ptoas" echo "Smoke testing packaged ptoas dist..." VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" +fi + echo "$VERSION_OUTPUT" -bash "$0" --check-ptoas-cli-identity "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +bash "${SCRIPT_DIR}/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +if [[ "$IDENTITY_CHECK_ONLY" == 1 ]]; then + exit 0 +fi # Show collected files echo "" diff --git a/docker/collect_ptoas_dist_mac.sh b/docker/collect_ptoas_dist_mac.sh index f16a4be2d1..eb53be7190 100644 --- a/docker/collect_ptoas_dist_mac.sh +++ b/docker/collect_ptoas_dist_mac.sh @@ -24,14 +24,17 @@ set -euo pipefail +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IDENTITY_CHECK_ONLY=0 if [[ "${1:-}" == "--check-ptoas-cli-identity" ]]; then if [[ $# -lt 2 || $# -gt 3 ]]; then echo "Usage: $0 --check-ptoas-cli-identity [product-version]" >&2 exit 2 fi - exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check_ptoas_cli_identity.sh" \ - "$2" "${3:-}" -fi + IDENTITY_CHECK_ONLY=1 + VERSION_OUTPUT=$2 + PTOAS_VERSION=${3:-} +else if [ $# -lt 1 ]; then echo "Usage: $0 " >&2 @@ -327,8 +330,13 @@ chmod +x "${PTOAS_DIST_DIR}/ptoas" echo "Smoke testing packaged ptoas dist..." VERSION_OUTPUT="$(env -u PYTHONPATH -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" --version | tr -d '\r')" +fi + echo "$VERSION_OUTPUT" -bash "$0" --check-ptoas-cli-identity "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +bash "${SCRIPT_DIR}/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +if [[ "$IDENTITY_CHECK_ONLY" == 1 ]]; then + exit 0 +fi env -u DYLD_LIBRARY_PATH -u LD_LIBRARY_PATH \ "${PTOAS_DIST_DIR}/ptoas" \ "${PTO_SOURCE_DIR}/test/lit/pto/kernel_kind_vector_scf_while_emitc.pto" \ diff --git a/docker/test_ptoas_cli.sh b/docker/test_ptoas_cli.sh index cf4b9700b7..4e7747fc81 100755 --- a/docker/test_ptoas_cli.sh +++ b/docker/test_ptoas_cli.sh @@ -18,14 +18,17 @@ set -e +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +IDENTITY_CHECK_ONLY=0 if [[ "${1:-}" == "--check-ptoas-cli-identity" ]]; then if [[ $# -lt 2 || $# -gt 3 ]]; then echo "Usage: $0 --check-ptoas-cli-identity [product-version]" >&2 exit 2 fi - exec bash "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check_ptoas_cli_identity.sh" \ - "$2" "${3:-}" -fi + IDENTITY_CHECK_ONLY=1 + VERSION_OUTPUT=$2 + PTOAS_VERSION=${3:-} +else # Validate required environment variables for var in PTO_SOURCE_DIR LLVM_BUILD_DIR PTO_INSTALL_DIR; do @@ -46,8 +49,13 @@ which ptoas echo "Checking ptoas version..." VERSION_OUTPUT="$(ptoas --version | tr -d '\r')" +fi + echo "$VERSION_OUTPUT" -bash "$0" --check-ptoas-cli-identity "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +bash "${SCRIPT_DIR}/check_ptoas_cli_identity.sh" "${VERSION_OUTPUT}" "${PTOAS_VERSION:-}" +if [[ "$IDENTITY_CHECK_ONLY" == 1 ]]; then + exit 0 +fi # Test MatMul sample echo "Testing MatMul sample..." diff --git a/tools/test_release_delivery_contract.py b/tools/test_release_delivery_contract.py index 0e4aeab68f..ee2d75a546 100644 --- a/tools/test_release_delivery_contract.py +++ b/tools/test_release_delivery_contract.py @@ -122,6 +122,36 @@ def test_dead_identity_entrypoints_fail_dynamic_validation(self) -> None: with self.subTest(body=body), self.assertRaises(SystemExit): validate_packaging_identity_mode(script) + def test_shared_production_identity_call_cannot_be_removed_or_bypassed( + self, + ) -> None: + shared_call = ( + 'bash "${SCRIPT_DIR}/check_ptoas_cli_identity.sh" ' + '"${VERSION_OUTPUT}" "${PTOAS_VERSION:-}"' + ) + helper_text = (ROOT / "docker/check_ptoas_cli_identity.sh").read_text() + with tempfile.TemporaryDirectory() as directory: + temp_root = Path(directory) + (temp_root / "check_ptoas_cli_identity.sh").write_text(helper_text) + for source in ( + ROOT / "docker/test_ptoas_cli.sh", + ROOT / "docker/collect_ptoas_dist.sh", + ROOT / "docker/collect_ptoas_dist_mac.sh", + ): + original = source.read_text() + self.assertEqual(original.count(shared_call), 1) + for label, replacement in ( + ("removed", ": # identity validation removed"), + ("bypassed", f"false && {shared_call}"), + ): + mutated = temp_root / f"{source.stem}-{label}.sh" + mutated.write_text(original.replace(shared_call, replacement, 1)) + with ( + self.subTest(script=source.name, mutation=label), + self.assertRaises(SystemExit), + ): + validate_packaging_identity_mode(mutated) + if __name__ == "__main__": unittest.main() From 57115e6fc6f412ed139532d4f5d99cf36938fa1e Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 09:37:44 +0800 Subject: [PATCH 29/32] ci: preserve resolved Python for nanobind --- .github/workflows/ci.yml | 1 - tools/test_check_python_binding_link_contract.py | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16f6246368..82c4bcd9b5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,7 +200,6 @@ jobs: -DLLVM_DIR="${LLVM_DIR}/lib/cmake/llvm" \ -DMLIR_DIR="${LLVM_DIR}/lib/cmake/mlir" \ -DPython3_EXECUTABLE=python3 \ - -DPython_EXECUTABLE=python3 \ -DPython3_FIND_STRATEGY=LOCATION \ -Dnanobind_DIR="${NANOBIND_CMAKE_DIR}" \ -DMLIR_ENABLE_BINDINGS_PYTHON=ON \ diff --git a/tools/test_check_python_binding_link_contract.py b/tools/test_check_python_binding_link_contract.py index ae7220b9b2..a1ade24715 100644 --- a/tools/test_check_python_binding_link_contract.py +++ b/tools/test_check_python_binding_link_contract.py @@ -47,7 +47,9 @@ def run_checker( check=False, ) - def test_rejects_shared_runtime_not_installed_in_auditwheel_search_path(self) -> None: + def test_rejects_shared_runtime_not_installed_in_auditwheel_search_path( + self, + ) -> None: result = self.run_checker( """ if(UNIX AND NOT APPLE) @@ -59,7 +61,9 @@ def test_rejects_shared_runtime_not_installed_in_auditwheel_search_path(self) -> self.assertNotEqual(result.returncode, 0) self.assertIn("install nanobind-mlir into lib", result.stderr) - def test_accepts_installed_runtime_and_matching_auditwheel_search_path(self) -> None: + def test_accepts_installed_runtime_and_matching_auditwheel_search_path( + self, + ) -> None: result = self.run_checker( VALID_PRODUCER, """ @@ -105,6 +109,13 @@ def test_rejects_search_path_only_in_unrelated_comment_and_step(self) -> None: self.assertNotEqual(result.returncode, 0) self.assertIn("Repair wheel with auditwheel", result.stderr) + def test_ci_does_not_override_nanobind_python_with_relative_executable( + self, + ) -> None: + workflow = (CHECKER.parents[1] / ".github/workflows/ci.yml").read_text() + self.assertIn("-DPython3_EXECUTABLE=python3", workflow) + self.assertNotIn("-DPython_EXECUTABLE=python3", workflow) + if __name__ == "__main__": unittest.main() From 8acc780b267b87cb4a65353327c542081a5c6c9f Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 09:54:39 +0800 Subject: [PATCH 30/32] fix: use stable LLVM 23 operand helper --- python/pto/dialects/pto.py | 7 +++-- tools/check_python_binding_link_contract.py | 23 ++++++++++++-- ...test_check_python_binding_link_contract.py | 31 ++++++++++++++++++- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/python/pto/dialects/pto.py b/python/pto/dialects/pto.py index abcedbb4e2..5f9ceefba2 100644 --- a/python/pto/dialects/pto.py +++ b/python/pto/dialects/pto.py @@ -13,6 +13,7 @@ from mlir import ir as _ods_ir from . import _pto_ops_gen as _pto_ops_gen +from ._ods_common import get_op_result_or_value as _ods_get_op_result_or_value def _load_local_pto_ext(): @@ -43,7 +44,7 @@ def _export_generated_symbols(): def get_op_result_or_value(value): - return getattr(_pto_ops_gen, "_get_op_result_or_value")(value) + return _ods_get_op_result_or_value(value) _export_generated_symbols() @@ -656,7 +657,7 @@ def _init_inferred(self, source, offsets, sizes, args, loc, ip): sizes, *args = args if args: raise TypeError(f"too many positional arguments: {len(args)}") - source_value = _pto_ops_gen._get_op_result_or_value(source) + source_value = get_op_result_or_value(source) source_type = source_value.type result = PartitionTensorViewType.get(source_type.rank, source_type.element_type) self._init_explicit(result, source_value, offsets, sizes, (), loc, ip) @@ -673,7 +674,7 @@ def _init_explicit(self, result, source, offsets, sizes, args, loc, ip): if args: raise TypeError(f"too many positional arguments: {len(args)}") operands = [ - _pto_ops_gen._get_op_result_or_value(source), + get_op_result_or_value(source), _pto_ops_gen._get_op_results_or_values(offsets), _pto_ops_gen._get_op_results_or_values(sizes), ] diff --git a/tools/check_python_binding_link_contract.py b/tools/check_python_binding_link_contract.py index 75b27f8721..1b3735b166 100644 --- a/tools/check_python_binding_link_contract.py +++ b/tools/check_python_binding_link_contract.py @@ -38,6 +38,25 @@ def main() -> int: cmake_path = args.ptoas_root / "lib" / "Bindings" / "Python" / "CMakeLists.txt" source = cmake_path.read_text(encoding="utf-8") + dialect_path = args.ptoas_root / "python" / "pto" / "dialects" / "pto.py" + dialect = dialect_path.read_text(encoding="utf-8") + stable_operand_import = ( + "from ._ods_common import get_op_result_or_value as _ods_get_op_result_or_value" + ) + if stable_operand_import not in dialect: + raise SystemExit( + "error: the PTO Python dialect must import the stable MLIR operand " + "conversion helper from _ods_common" + ) + if ( + "_pto_ops_gen._get_op_result_or_value" in dialect + or 'getattr(_pto_ops_gen, "_get_op_result_or_value")' in dialect + ): + raise SystemExit( + "error: LLVM 23 generated dialect modules do not export the singular " + "_get_op_result_or_value helper" + ) + contract = re.compile( r"if\s*\(UNIX\s+AND\s+NOT\s+APPLE\).*?" r"target_link_options\s*\(nanobind-mlir\s+PRIVATE\s+" @@ -83,9 +102,7 @@ def main() -> int: "error: Repair wheel with auditwheel must export LD_LIBRARY_PATH " "including $PTO_INSTALL_DIR/lib" ) - if not re.search( - r"(?m)^\s*auditwheel\s+repair(?:\s|$)", active_repair_lines - ): + if not re.search(r"(?m)^\s*auditwheel\s+repair(?:\s|$)", active_repair_lines): raise SystemExit( "error: Repair wheel with auditwheel must invoke auditwheel repair " "in the same step that exports LD_LIBRARY_PATH" diff --git a/tools/test_check_python_binding_link_contract.py b/tools/test_check_python_binding_link_contract.py index a1ade24715..3bfa8ee6cd 100644 --- a/tools/test_check_python_binding_link_contract.py +++ b/tools/test_check_python_binding_link_contract.py @@ -26,11 +26,17 @@ ) endif() """ +VALID_DIALECT = """ +from ._ods_common import get_op_result_or_value as _ods_get_op_result_or_value + +def get_op_result_or_value(value): + return _ods_get_op_result_or_value(value) +""" class PythonBindingLinkContractTest(unittest.TestCase): def run_checker( - self, cmake_source: str, workflow: str + self, cmake_source: str, workflow: str, dialect_source: str = VALID_DIALECT ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as directory: root = Path(directory) @@ -40,6 +46,9 @@ def run_checker( workflow_path = root / ".github/workflows/build_wheel.yml" workflow_path.parent.mkdir(parents=True) workflow_path.write_text(workflow) + dialect_path = root / "python/pto/dialects/pto.py" + dialect_path.parent.mkdir(parents=True) + dialect_path.write_text(dialect_source) return subprocess.run( ["python3", str(CHECKER), "--ptoas-root", str(root)], text=True, @@ -116,6 +125,26 @@ def test_ci_does_not_override_nanobind_python_with_relative_executable( self.assertIn("-DPython3_EXECUTABLE=python3", workflow) self.assertNotIn("-DPython_EXECUTABLE=python3", workflow) + def test_rejects_removed_llvm23_generated_operand_helper(self) -> None: + result = self.run_checker( + VALID_PRODUCER, + """ +jobs: + build: + steps: + - name: Repair wheel with auditwheel + run: | + export LD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH + auditwheel repair dist/ptoas.whl -w wheelhouse +""", + """ +def get_op_result_or_value(value): + return _pto_ops_gen._get_op_result_or_value(value) +""", + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("stable MLIR operand conversion helper", result.stderr) + if __name__ == "__main__": unittest.main() From 797bf01d73c13a985ac82b6686a4a2e8cbe11f4c Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 10:12:06 +0800 Subject: [PATCH 31/32] fix: migrate LLVM 23 SCF sample API --- lib/PTO/IR/PTO.cpp | 13 ++++++++- test/lit/pto/tstore_fp_a3_memref_verifier.pto | 13 +++++++++ test/samples/AsyncComm/async_comm.py | 2 +- .../AsyncComm/tget_async_kernel_impl_like.py | 12 ++++----- .../AsyncComm/tput_async_kernel_impl_like.py | 12 ++++----- .../CommSync/tbroadcast_root_binding.py | 2 +- test/samples/CommSync/tgather_root_binding.py | 2 +- test/samples/CommSync/treduce_root_binding.py | 2 +- .../samples/CommSync/tscatter_root_binding.py | 2 +- test/samples/CommSync/twait_atomic_binding.py | 2 +- test/samples/MatMul/tmatmulk.py | 8 +++--- .../Matmul_transpose/Matmul_transpose.py | 8 +++--- test/samples/Sync/test_inject_sync_if.py | 2 +- test/samples/Sync/test_inject_sync_if_else.py | 2 +- test/samples/Sync/test_inject_sync_loop.py | 2 +- .../Sync/test_inject_sync_loop_nest.py | 2 +- test/samples/Sync/test_intercore_sync_a3.py | 2 +- .../Sync/test_intercore_sync_a3_dyn.py | 2 +- .../Sync/test_mem_inject_sync_basic.py | 2 +- test/samples/Sync/tmatmulk_autosync.py | 8 +++--- test/samples/Sync/tmatmulk_autosync_a5.py | 8 +++--- tools/check_python_binding_link_contract.py | 11 ++++++++ ...test_check_python_binding_link_contract.py | 27 ++++++++++++++++++- 23 files changed, 103 insertions(+), 43 deletions(-) create mode 100644 test/lit/pto/tstore_fp_a3_memref_verifier.pto diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index c6a90a4892..a658ed6555 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -9058,7 +9058,18 @@ mlir::LogicalResult mlir::pto::TStoreFPOp::verify() { return emitOpError() << "expects src to be in the acc address space"; return mlir::success(); }; - return dispatchVerifierByArch(getOperation(), verifyA2A3, verifyA5); + // TSTORE_FP's architectural form always includes a GM destination memref, + // so the legacy decoded-memref bypass would disable its verifier entirely. + // Dispatch explicitly and retain the fail-closed Linx boundary. + switch (getVerifierTargetArch(getOperation())) { + case VerifierTargetArch::A2A3: + return verifyA2A3(); + case VerifierTargetArch::A5: + return verifyA5(); + case VerifierTargetArch::Linx: + return emitOpError("Linx legality is not implemented for this operation"); + } + return failure(); } mlir::LogicalResult mlir::pto::TSubOp::verify() { diff --git a/test/lit/pto/tstore_fp_a3_memref_verifier.pto b/test/lit/pto/tstore_fp_a3_memref_verifier.pto new file mode 100644 index 0000000000..f067da630e --- /dev/null +++ b/test/lit/pto/tstore_fp_a3_memref_verifier.pto @@ -0,0 +1,13 @@ +// RUN: not ptoas --pto-arch=a3 %s -o /dev/null 2>&1 | FileCheck %s + +module attributes {pto.target_arch = "a3"} { + func.func @tstore_fp_vec_source_must_not_bypass( + %src: !pto.tile_buf, + %fp: !pto.tile_buf, + %dst: memref<16x32xi8, #pto.address_space>) { + pto.tstore_fp ins(%src, %fp : !pto.tile_buf, !pto.tile_buf) outs(%dst : memref<16x32xi8, #pto.address_space>) + return + } +} + +// CHECK: error: 'pto.tstore_fp' op expects src to be in the acc address space diff --git a/test/samples/AsyncComm/async_comm.py b/test/samples/AsyncComm/async_comm.py index 36a11b3b96..72c15e744e 100644 --- a/test/samples/AsyncComm/async_comm.py +++ b/test/samples/AsyncComm/async_comm.py @@ -80,7 +80,7 @@ def build(): single_rank = arith.CmpIOp( arith.CmpIPredicate.sle, nranks, c1_i32 ).result - guarded = scf.IfOp(single_rank, [], hasElse=True) + guarded = scf.IfOp(single_rank, [], has_else=True) with InsertionPoint(guarded.then_block): scf.YieldOp([]) diff --git a/test/samples/AsyncComm/tget_async_kernel_impl_like.py b/test/samples/AsyncComm/tget_async_kernel_impl_like.py index 8dedd33897..4d40c05995 100644 --- a/test/samples/AsyncComm/tget_async_kernel_impl_like.py +++ b/test/samples/AsyncComm/tget_async_kernel_impl_like.py @@ -151,7 +151,7 @@ def build(): arith.AndIOp(count_gt_zero, offset_ge_zero).result, end_le_bound ).result - valid_if = scf.IfOp(valid, [], hasElse=False) + valid_if = scf.IfOp(valid, [], has_else=False) with InsertionPoint(valid_if.then_block): pto.barrier(pipe_all) @@ -161,7 +161,7 @@ def build(): is_root = arith.CmpIOp( arith.CmpIPredicate.eq, my_rank, root_rank ).result - root_if = scf.IfOp(is_root, [], hasElse=False) + root_if = scf.IfOp(is_root, [], has_else=False) with InsertionPoint(root_if.then_block): nranks_idx = arith.IndexCastOp(idx, nranks).result @@ -172,13 +172,13 @@ def build(): is_not_self = arith.CmpIOp( arith.CmpIPredicate.ne, target_rank_i32, root_rank ).result - target_if = scf.IfOp(is_not_self, [], hasElse=False) + target_if = scf.IfOp(is_not_self, [], has_else=False) with InsertionPoint(target_if.then_block): is_rank1 = arith.CmpIOp( arith.CmpIPredicate.eq, target_rank_i32, c1_i32 ).result - rank1_if = scf.IfOp(is_rank1, [], hasElse=False) + rank1_if = scf.IfOp(is_rank1, [], has_else=False) with InsertionPoint(rank1_if.then_block): event1 = _tget_async(dst_rank1, src_rank1, session, ctx) _wait_after_async(event1, session) @@ -187,7 +187,7 @@ def build(): is_rank2 = arith.CmpIOp( arith.CmpIPredicate.eq, target_rank_i32, c2_i32 ).result - rank2_if = scf.IfOp(is_rank2, [], hasElse=False) + rank2_if = scf.IfOp(is_rank2, [], has_else=False) with InsertionPoint(rank2_if.then_block): event2 = _tget_async(dst_rank2, src_rank2, session, ctx) _wait_after_async(event2, session) @@ -196,7 +196,7 @@ def build(): is_rank3 = arith.CmpIOp( arith.CmpIPredicate.eq, target_rank_i32, c3_i32 ).result - rank3_if = scf.IfOp(is_rank3, [], hasElse=False) + rank3_if = scf.IfOp(is_rank3, [], has_else=False) with InsertionPoint(rank3_if.then_block): event3 = _tget_async(dst_rank3, src_rank3, session, ctx) _wait_after_async(event3, session) diff --git a/test/samples/AsyncComm/tput_async_kernel_impl_like.py b/test/samples/AsyncComm/tput_async_kernel_impl_like.py index 1c8d8d4d71..6b2df20107 100644 --- a/test/samples/AsyncComm/tput_async_kernel_impl_like.py +++ b/test/samples/AsyncComm/tput_async_kernel_impl_like.py @@ -143,7 +143,7 @@ def build(): arith.AndIOp(count_gt_zero, offset_ge_zero).result, end_le_bound ).result - valid_if = scf.IfOp(valid, [], hasElse=False) + valid_if = scf.IfOp(valid, [], has_else=False) with InsertionPoint(valid_if.then_block): scratch = pto.AllocTileOp(scratch_ty).result @@ -152,7 +152,7 @@ def build(): is_root = arith.CmpIOp( arith.CmpIPredicate.eq, my_rank, root_rank ).result - root_if = scf.IfOp(is_root, [], hasElse=False) + root_if = scf.IfOp(is_root, [], has_else=False) with InsertionPoint(root_if.then_block): nranks_idx = arith.IndexCastOp(idx, nranks).result @@ -163,13 +163,13 @@ def build(): is_not_self = arith.CmpIOp( arith.CmpIPredicate.ne, target_rank_i32, root_rank ).result - target_if = scf.IfOp(is_not_self, [], hasElse=False) + target_if = scf.IfOp(is_not_self, [], has_else=False) with InsertionPoint(target_if.then_block): is_rank1 = arith.CmpIOp( arith.CmpIPredicate.eq, target_rank_i32, c1_i32 ).result - rank1_if = scf.IfOp(is_rank1, [], hasElse=False) + rank1_if = scf.IfOp(is_rank1, [], has_else=False) with InsertionPoint(rank1_if.then_block): event1 = _tput_async(dst_rank1, src, session, ctx) _wait_after_async(event1, session) @@ -178,7 +178,7 @@ def build(): is_rank2 = arith.CmpIOp( arith.CmpIPredicate.eq, target_rank_i32, c2_i32 ).result - rank2_if = scf.IfOp(is_rank2, [], hasElse=False) + rank2_if = scf.IfOp(is_rank2, [], has_else=False) with InsertionPoint(rank2_if.then_block): event2 = _tput_async(dst_rank2, src, session, ctx) _wait_after_async(event2, session) @@ -187,7 +187,7 @@ def build(): is_rank3 = arith.CmpIOp( arith.CmpIPredicate.eq, target_rank_i32, c3_i32 ).result - rank3_if = scf.IfOp(is_rank3, [], hasElse=False) + rank3_if = scf.IfOp(is_rank3, [], has_else=False) with InsertionPoint(rank3_if.then_block): event3 = _tput_async(dst_rank3, src, session, ctx) _wait_after_async(event3, session) diff --git a/test/samples/CommSync/tbroadcast_root_binding.py b/test/samples/CommSync/tbroadcast_root_binding.py index a79ecd759b..272044d68f 100644 --- a/test/samples/CommSync/tbroadcast_root_binding.py +++ b/test/samples/CommSync/tbroadcast_root_binding.py @@ -59,7 +59,7 @@ def build(): ping = pto.AllocTileOp(tb_f32).result is_root = arith.CmpIOp(arith.CmpIPredicate.eq, my_rank, c1_i32).result - root_if = scf.IfOp(is_root, [], hasElse=False) + root_if = scf.IfOp(is_root, [], has_else=False) with InsertionPoint(root_if.then_block): pto.TBroadcastOp(src, ping, group, 1) scf.YieldOp([]) diff --git a/test/samples/CommSync/tgather_root_binding.py b/test/samples/CommSync/tgather_root_binding.py index 10e688b231..2ba70cc59e 100644 --- a/test/samples/CommSync/tgather_root_binding.py +++ b/test/samples/CommSync/tgather_root_binding.py @@ -59,7 +59,7 @@ def build(): ping = pto.AllocTileOp(tb_f32).result is_root = arith.CmpIOp(arith.CmpIPredicate.eq, my_rank, c1_i32).result - root_if = scf.IfOp(is_root, [], hasElse=False) + root_if = scf.IfOp(is_root, [], has_else=False) with InsertionPoint(root_if.then_block): pto.CommTGatherOp(dst, ping, group, 1) scf.YieldOp([]) diff --git a/test/samples/CommSync/treduce_root_binding.py b/test/samples/CommSync/treduce_root_binding.py index 4d3d0de38a..02ed0d50ce 100644 --- a/test/samples/CommSync/treduce_root_binding.py +++ b/test/samples/CommSync/treduce_root_binding.py @@ -60,7 +60,7 @@ def build(): recv = pto.AllocTileOp(tb_f32).result is_root = arith.CmpIOp(arith.CmpIPredicate.eq, my_rank, c1_i32).result - root_if = scf.IfOp(is_root, [], hasElse=False) + root_if = scf.IfOp(is_root, [], has_else=False) with InsertionPoint(root_if.then_block): pto.TReduceOp( dst, diff --git a/test/samples/CommSync/tscatter_root_binding.py b/test/samples/CommSync/tscatter_root_binding.py index b7441cbdb6..5e0022569b 100644 --- a/test/samples/CommSync/tscatter_root_binding.py +++ b/test/samples/CommSync/tscatter_root_binding.py @@ -59,7 +59,7 @@ def build(): ping = pto.AllocTileOp(tb_f32).result is_root = arith.CmpIOp(arith.CmpIPredicate.eq, my_rank, c1_i32).result - root_if = scf.IfOp(is_root, [], hasElse=False) + root_if = scf.IfOp(is_root, [], has_else=False) with InsertionPoint(root_if.then_block): pto.CommTScatterOp(src, ping, group, 1) scf.YieldOp([]) diff --git a/test/samples/CommSync/twait_atomic_binding.py b/test/samples/CommSync/twait_atomic_binding.py index 60be0becb4..ee54eca93c 100644 --- a/test/samples/CommSync/twait_atomic_binding.py +++ b/test/samples/CommSync/twait_atomic_binding.py @@ -46,7 +46,7 @@ def build(): ).result is_non_root = arith.CmpIOp(arith.CmpIPredicate.ne, my_rank, c0_i32).result - branch = scf.IfOp(is_non_root, [], hasElse=True) + branch = scf.IfOp(is_non_root, [], has_else=True) with InsertionPoint(branch.then_block): iters_idx = arith.IndexCastOp(idx, iters).result diff --git a/test/samples/MatMul/tmatmulk.py b/test/samples/MatMul/tmatmulk.py index 40f071b660..3980a631d8 100644 --- a/test/samples/MatMul/tmatmulk.py +++ b/test/samples/MatMul/tmatmulk.py @@ -198,7 +198,7 @@ def build( pto.TLoadOp(None, svA, aMatTile) pto.TLoadOp(None, svB, bMatTile) - if_load_bias = scf.IfOp(isBias, [], hasElse=True) + if_load_bias = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_load_bias.then_block): pto.TLoadOp(None, svBias, biasDataTile) scf.YieldOp([]) @@ -214,7 +214,7 @@ def build( pto.TMovOp(None, aMatTile, aTile) pto.TMovOp(None, bMatTile, bTile) - if_mov_bias = scf.IfOp(isBias, [], hasElse=True) + if_mov_bias = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_mov_bias.then_block): pto.TMovOp(None, biasDataTile, biasTile) scf.YieldOp([]) @@ -227,11 +227,11 @@ def build( # ---- i == 0 ? (bias? TMATMUL_BIAS : TMATMUL) : TMATMUL_ACC ---- is_i0 = arith.CmpIOp(CmpIPredicate.eq, i, c0).result - if_i0 = scf.IfOp(is_i0, [], hasElse=True) + if_i0 = scf.IfOp(is_i0, [], has_else=True) # then: i == 0 with InsertionPoint(if_i0.then_block): - if_bias0 = scf.IfOp(isBias, [], hasElse=True) + if_bias0 = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_bias0.then_block): # L0C 清空 + bias # 约定:valid_dims_c 用于 C 的有效区域 diff --git a/test/samples/Matmul_transpose/Matmul_transpose.py b/test/samples/Matmul_transpose/Matmul_transpose.py index 1f03f63538..431fc34f13 100644 --- a/test/samples/Matmul_transpose/Matmul_transpose.py +++ b/test/samples/Matmul_transpose/Matmul_transpose.py @@ -200,7 +200,7 @@ def build( c_tile = pto.AllocTileOp(tile_buf_c_tile).result # ---- TLOAD: 按 isAtranspose / isBtranspose 选 view 与 MAT tile ---- - if_a = scf.IfOp(is_a_transpose, [], hasElse=True) + if_a = scf.IfOp(is_a_transpose, [], has_else=True) with InsertionPoint(if_a.then_block): pto.TLoadOp(None, sv_a_dn, a_mat_dn) scf.YieldOp([]) @@ -208,7 +208,7 @@ def build( pto.TLoadOp(None, sv_a_nd, a_mat_nd) scf.YieldOp([]) - if_b = scf.IfOp(is_b_transpose, [], hasElse=True) + if_b = scf.IfOp(is_b_transpose, [], has_else=True) with InsertionPoint(if_b.then_block): pto.TLoadOp(None, sv_b_nd, b_mat_nd) scf.YieldOp([]) @@ -220,7 +220,7 @@ def build( pto.wait_event(TLOAD, TMOV_M2L, EVENT_ID0) # ---- TEXTRACT: 按 isAtranspose / isBtranspose 从对应 MAT 取到 L0 ---- - if_a_ext = scf.IfOp(is_a_transpose, [], hasElse=True) + if_a_ext = scf.IfOp(is_a_transpose, [], has_else=True) with InsertionPoint(if_a_ext.then_block): pto.TExtractOp(a_mat_dn, cIndexM, cIndexK, a_tile) scf.YieldOp([]) @@ -228,7 +228,7 @@ def build( pto.TExtractOp(a_mat_nd, cIndexM, cIndexK, a_tile) scf.YieldOp([]) - if_b_ext = scf.IfOp(is_b_transpose, [], hasElse=True) + if_b_ext = scf.IfOp(is_b_transpose, [], has_else=True) with InsertionPoint(if_b_ext.then_block): pto.TExtractOp(b_mat_nd, cIndexK, cIndexN, b_tile) scf.YieldOp([]) diff --git a/test/samples/Sync/test_inject_sync_if.py b/test/samples/Sync/test_inject_sync_if.py index 4e0e6f452d..0a2a0ad7e3 100644 --- a/test/samples/Sync/test_inject_sync_if.py +++ b/test/samples/Sync/test_inject_sync_if.py @@ -74,7 +74,7 @@ def build(): pto.TLoadOp(None, sv_in, ub0) pto.TLoadOp(None, sv_in, ub1) - if_op = scf.IfOp(cond, [], hasElse=False) + if_op = scf.IfOp(cond, [], has_else=False) with InsertionPoint(if_op.then_block): pto.TAddOp(ub0, ub0, ub1) scf.YieldOp([]) diff --git a/test/samples/Sync/test_inject_sync_if_else.py b/test/samples/Sync/test_inject_sync_if_else.py index 73acb33f57..6659b43e82 100644 --- a/test/samples/Sync/test_inject_sync_if_else.py +++ b/test/samples/Sync/test_inject_sync_if_else.py @@ -70,7 +70,7 @@ def build(): pto.TLoadOp(None, sv_in, ub) - if_op = scf.IfOp(cond, [], hasElse=True) + if_op = scf.IfOp(cond, [], has_else=True) with InsertionPoint(if_op.then_block): pto.TAddOp(ub, ub, ub) scf.YieldOp([]) diff --git a/test/samples/Sync/test_inject_sync_loop.py b/test/samples/Sync/test_inject_sync_loop.py index 5997c733b7..eb07802ccd 100644 --- a/test/samples/Sync/test_inject_sync_loop.py +++ b/test/samples/Sync/test_inject_sync_loop.py @@ -75,7 +75,7 @@ def build(): pto.TLoadOp(None, sv_in, ub) is_i0 = arith.CmpIOp(CmpIPredicate.eq, i, c0).result - if_op = scf.IfOp(is_i0, [], hasElse=False) + if_op = scf.IfOp(is_i0, [], has_else=False) with InsertionPoint(if_op.then_block): pto.TAddOp(ub, ub, ub) scf.YieldOp([]) diff --git a/test/samples/Sync/test_inject_sync_loop_nest.py b/test/samples/Sync/test_inject_sync_loop_nest.py index 636d6e463f..c26008c0fb 100644 --- a/test/samples/Sync/test_inject_sync_loop_nest.py +++ b/test/samples/Sync/test_inject_sync_loop_nest.py @@ -77,7 +77,7 @@ def build(): pto.TLoadOp(None, sv_in, ub) is_j0 = arith.CmpIOp(CmpIPredicate.eq, j, c0).result - if_op = scf.IfOp(is_j0, [], hasElse=False) + if_op = scf.IfOp(is_j0, [], has_else=False) with InsertionPoint(if_op.then_block): pto.TAddOp(ub, ub, ub) scf.YieldOp([]) diff --git a/test/samples/Sync/test_intercore_sync_a3.py b/test/samples/Sync/test_intercore_sync_a3.py index 01aab0a797..4f16ca4292 100644 --- a/test/samples/Sync/test_intercore_sync_a3.py +++ b/test/samples/Sync/test_intercore_sync_a3.py @@ -52,7 +52,7 @@ def build(): should_wait = arith.CmpIOp( arith.CmpIPredicate.eq, entry.arguments[2], c0_i32 ).result - if_op = scf.IfOp(should_wait, [], hasElse=False) + if_op = scf.IfOp(should_wait, [], has_else=False) with InsertionPoint(if_op.then_block): pto.sync_wait(pipe_v, 3) scf.YieldOp([]) diff --git a/test/samples/Sync/test_intercore_sync_a3_dyn.py b/test/samples/Sync/test_intercore_sync_a3_dyn.py index 109bf9f4e8..ec9acd09e6 100644 --- a/test/samples/Sync/test_intercore_sync_a3_dyn.py +++ b/test/samples/Sync/test_intercore_sync_a3_dyn.py @@ -52,7 +52,7 @@ def build(): should_wait = arith.CmpIOp( arith.CmpIPredicate.eq, entry.arguments[2], c0_i32 ).result - if_op = scf.IfOp(should_wait, [], hasElse=False) + if_op = scf.IfOp(should_wait, [], has_else=False) with InsertionPoint(if_op.then_block): pto.sync_wait(pipe_v, c3) scf.YieldOp([]) diff --git a/test/samples/Sync/test_mem_inject_sync_basic.py b/test/samples/Sync/test_mem_inject_sync_basic.py index 7b710deaf3..8f88b573b0 100644 --- a/test/samples/Sync/test_mem_inject_sync_basic.py +++ b/test/samples/Sync/test_mem_inject_sync_basic.py @@ -72,7 +72,7 @@ def build(): # MTE2(TLOAD) -> V(TADD, conditional) -> MTE3(TSTORE) pto.TLoadOp(None, sv_in, ub) - if_op = scf.IfOp(cond, [], hasElse=False) + if_op = scf.IfOp(cond, [], has_else=False) with InsertionPoint(if_op.then_block): pto.TAddOp(ub, ub, ub) scf.YieldOp([]) diff --git a/test/samples/Sync/tmatmulk_autosync.py b/test/samples/Sync/tmatmulk_autosync.py index 74d0e82a04..3972d1722d 100644 --- a/test/samples/Sync/tmatmulk_autosync.py +++ b/test/samples/Sync/tmatmulk_autosync.py @@ -190,7 +190,7 @@ def build( pto.TLoadOp(None, svA, aMatTile) pto.TLoadOp(None, svB, bMatTile) - if_load_bias = scf.IfOp(isBias, [], hasElse=True) + if_load_bias = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_load_bias.then_block): pto.TLoadOp(None, svBias, biasDataTile) scf.YieldOp([]) @@ -201,7 +201,7 @@ def build( pto.TMovOp(None, aMatTile, aTile) pto.TMovOp(None, bMatTile, bTile) - if_mov_bias = scf.IfOp(isBias, [], hasElse=True) + if_mov_bias = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_mov_bias.then_block): pto.TMovOp(None, biasDataTile, biasTile) scf.YieldOp([]) @@ -210,10 +210,10 @@ def build( # ---- i == 0 ? (bias? TMATMUL_BIAS : TMATMUL) : TMATMUL_ACC ---- is_i0 = arith.CmpIOp(CmpIPredicate.eq, i, c0).result - if_i0 = scf.IfOp(is_i0, [], hasElse=True) + if_i0 = scf.IfOp(is_i0, [], has_else=True) with InsertionPoint(if_i0.then_block): - if_bias0 = scf.IfOp(isBias, [], hasElse=True) + if_bias0 = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_bias0.then_block): pto.TMatmulBiasOp(None, aTile, bTile, biasTile, cTile) scf.YieldOp([]) diff --git a/test/samples/Sync/tmatmulk_autosync_a5.py b/test/samples/Sync/tmatmulk_autosync_a5.py index 579986d171..a168108039 100644 --- a/test/samples/Sync/tmatmulk_autosync_a5.py +++ b/test/samples/Sync/tmatmulk_autosync_a5.py @@ -191,7 +191,7 @@ def build( pto.TLoadOp(None, svA, aMatTile) pto.TLoadOp(None, svB, bMatTile) - if_load_bias = scf.IfOp(isBias, [], hasElse=True) + if_load_bias = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_load_bias.then_block): pto.TLoadOp(None, svBias, biasDataTile) scf.YieldOp([]) @@ -202,7 +202,7 @@ def build( pto.TMovOp(None, aMatTile, aTile) pto.TMovOp(None, bMatTile, bTile) - if_mov_bias = scf.IfOp(isBias, [], hasElse=True) + if_mov_bias = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_mov_bias.then_block): pto.TMovOp(None, biasDataTile, biasTile) scf.YieldOp([]) @@ -211,10 +211,10 @@ def build( # ---- i == 0 ? (bias? TMATMUL_BIAS : TMATMUL) : TMATMUL_ACC ---- is_i0 = arith.CmpIOp(CmpIPredicate.eq, i, c0).result - if_i0 = scf.IfOp(is_i0, [], hasElse=True) + if_i0 = scf.IfOp(is_i0, [], has_else=True) with InsertionPoint(if_i0.then_block): - if_bias0 = scf.IfOp(isBias, [], hasElse=True) + if_bias0 = scf.IfOp(isBias, [], has_else=True) with InsertionPoint(if_bias0.then_block): pto.TMatmulBiasOp(None, aTile, bTile, biasTile, cTile) scf.YieldOp([]) diff --git a/tools/check_python_binding_link_contract.py b/tools/check_python_binding_link_contract.py index 1b3735b166..e77e10f415 100644 --- a/tools/check_python_binding_link_contract.py +++ b/tools/check_python_binding_link_contract.py @@ -57,6 +57,17 @@ def main() -> int: "_get_op_result_or_value helper" ) + legacy_scf_samples = sorted( + path.relative_to(args.ptoas_root) + for path in (args.ptoas_root / "test" / "samples").rglob("*.py") + if re.search(r"\bhasElse\s*=", path.read_text(encoding="utf-8")) + ) + if legacy_scf_samples: + raise SystemExit( + "error: LLVM 23 SCF IfOp uses has_else, not hasElse: " + + ", ".join(map(str, legacy_scf_samples)) + ) + contract = re.compile( r"if\s*\(UNIX\s+AND\s+NOT\s+APPLE\).*?" r"target_link_options\s*\(nanobind-mlir\s+PRIVATE\s+" diff --git a/tools/test_check_python_binding_link_contract.py b/tools/test_check_python_binding_link_contract.py index 3bfa8ee6cd..3b2fd59e74 100644 --- a/tools/test_check_python_binding_link_contract.py +++ b/tools/test_check_python_binding_link_contract.py @@ -36,7 +36,11 @@ def get_op_result_or_value(value): class PythonBindingLinkContractTest(unittest.TestCase): def run_checker( - self, cmake_source: str, workflow: str, dialect_source: str = VALID_DIALECT + self, + cmake_source: str, + workflow: str, + dialect_source: str = VALID_DIALECT, + sample_source: str | None = None, ) -> subprocess.CompletedProcess[str]: with tempfile.TemporaryDirectory() as directory: root = Path(directory) @@ -49,6 +53,10 @@ def run_checker( dialect_path = root / "python/pto/dialects/pto.py" dialect_path.parent.mkdir(parents=True) dialect_path.write_text(dialect_source) + if sample_source is not None: + sample_path = root / "test/samples/Example/example.py" + sample_path.parent.mkdir(parents=True) + sample_path.write_text(sample_source) return subprocess.run( ["python3", str(CHECKER), "--ptoas-root", str(root)], text=True, @@ -145,6 +153,23 @@ def get_op_result_or_value(value): self.assertNotEqual(result.returncode, 0) self.assertIn("stable MLIR operand conversion helper", result.stderr) + def test_rejects_legacy_scf_if_keyword(self) -> None: + result = self.run_checker( + VALID_PRODUCER, + """ +jobs: + build: + steps: + - name: Repair wheel with auditwheel + run: | + export LD_LIBRARY_PATH=$LLVM_BUILD_DIR/lib:$PTO_INSTALL_DIR/lib:$LD_LIBRARY_PATH + auditwheel repair dist/ptoas.whl -w wheelhouse +""", + sample_source="branch = scf.IfOp(cond, [], hasElse = True)\n", + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("SCF IfOp uses has_else, not hasElse", result.stderr) + if __name__ == "__main__": unittest.main() From f1f931a3f5745f4f518f40d60bfb39edb8458998 Mon Sep 17 00:00:00 2001 From: RuoyuZhou Date: Mon, 17 Aug 2026 10:22:46 +0800 Subject: [PATCH 32/32] fix: verify TSTORE_FP tile sources --- lib/PTO/IR/PTO.cpp | 20 +++++++++++++++++--- test/lit/pto/tstore_fp_a3_decoded_memref.pto | 11 +++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 test/lit/pto/tstore_fp_a3_decoded_memref.pto diff --git a/lib/PTO/IR/PTO.cpp b/lib/PTO/IR/PTO.cpp index a658ed6555..88ee26a42b 100644 --- a/lib/PTO/IR/PTO.cpp +++ b/lib/PTO/IR/PTO.cpp @@ -387,6 +387,18 @@ static bool shouldBypassDecodedMemrefVerifier(Operation *op) { return false; } +static bool shouldBypassDecodedTileInputsVerifier(Operation *op, Value src, + Value aux) { + if (!op || isVerifierTargetLinx(op)) + return false; + for (Value operand : {src, aux}) { + if (isa(operand.getType()) || + operand.getDefiningOp()) + return true; + } + return false; +} + static SmallVector canonicalizeTileBufValidShape(ArrayRef validShape) { SmallVector canonical; canonical.reserve(validShape.size()); @@ -9058,9 +9070,11 @@ mlir::LogicalResult mlir::pto::TStoreFPOp::verify() { return emitOpError() << "expects src to be in the acc address space"; return mlir::success(); }; - // TSTORE_FP's architectural form always includes a GM destination memref, - // so the legacy decoded-memref bypass would disable its verifier entirely. - // Dispatch explicitly and retain the fail-closed Linx boundary. + // The GM destination is always a memref and must not disable verification. + // Only legacy decoded source/fp representations bypass A2/A3/A5 tile-form + // checks; Linx remains fail-closed. + if (shouldBypassDecodedTileInputsVerifier(getOperation(), getSrc(), getFp())) + return success(); switch (getVerifierTargetArch(getOperation())) { case VerifierTargetArch::A2A3: return verifyA2A3(); diff --git a/test/lit/pto/tstore_fp_a3_decoded_memref.pto b/test/lit/pto/tstore_fp_a3_decoded_memref.pto new file mode 100644 index 0000000000..7eb26d9892 --- /dev/null +++ b/test/lit/pto/tstore_fp_a3_decoded_memref.pto @@ -0,0 +1,11 @@ +// RUN: ptoas --pto-arch=a3 %s -o /dev/null + +module attributes {pto.target_arch = "a3"} { + func.func @tstore_fp_decoded_memrefs( + %src: memref<16x32xf32, #pto.address_space>, + %fp: memref<1x16xui64, #pto.address_space>, + %dst: memref<16x32xi8, #pto.address_space>) { + pto.tstore_fp ins(%src, %fp : memref<16x32xf32, #pto.address_space>, memref<1x16xui64, #pto.address_space>) outs(%dst : memref<16x32xi8, #pto.address_space>) + return + } +}