From e014835cf6abc4ef7b0a4867a7d8fef8da18c03a Mon Sep 17 00:00:00 2001 From: LinxISA Automation Date: Wed, 28 Oct 2026 16:33:59 +0800 Subject: [PATCH] Add experimental typed PredicateCell consumer adapters --- .github/workflows/pr-gate.yml | 7 + docs/tileop-usage/README.md | 3 + docs/tileop-usage/element-if.md | 31 ++++ docs/tileop-usage/options.md | 8 + docs/tileop-usage/predicate-adapters.md | 97 ++++++++++++ docs/tileop-usage/support-status.md | 15 +- .../logical/TCMPS.md | 8 + docs/tileop-usage/tlsu/memory/MSCATTER_ADD.md | 9 ++ .../tileop-usage/tlsu/memory/MSCATTER_MASK.md | 11 +- include/common/pto_predicate.hpp | 148 ++++++++++++++++++ include/common/pto_tileop.hpp | 1 + test/test_predicate_adapters.py | 101 ++++++++++++ 12 files changed, 437 insertions(+), 2 deletions(-) create mode 100644 docs/tileop-usage/predicate-adapters.md create mode 100644 include/common/pto_predicate.hpp create mode 100644 test/test_predicate_adapters.py diff --git a/.github/workflows/pr-gate.yml b/.github/workflows/pr-gate.yml index 03b4785e..f99a70c5 100644 --- a/.github/workflows/pr-gate.yml +++ b/.github/workflows/pr-gate.yml @@ -240,6 +240,13 @@ jobs: LINX_SYSROOT: ${{ env.TOOLCHAIN_INSTALL_DIR }}/sysroot run: python3 test/test_llvm138_narrow_tcmp.py -v + - name: Check typed PredicateCell producer and consumers + working-directory: tileop-api + env: + LINX_CXX: ${{ env.TOOLCHAIN_INSTALL_DIR }}/bin/clang++ + LINX_SYSROOT: ${{ env.TOOLCHAIN_INSTALL_DIR }}/sysroot + run: python3 test/test_predicate_adapters.py -v + - name: Run DType macro regression with associated APIs working-directory: tileop-api env: diff --git a/docs/tileop-usage/README.md b/docs/tileop-usage/README.md index ebc6fe16..89792f74 100644 --- a/docs/tileop-usage/README.md +++ b/docs/tileop-usage/README.md @@ -151,6 +151,9 @@ clang++ --target=linx64v5-unknown-linux-musl -mlxbc -fenable-matrix \ - 需要从 GM 传输数据时,阅读 [TLSU load/store/move](tlsu/load-store-move/TLOAD.md)。 其中卷积权重 GM 到 Shared NK 的 `OHWI2NK`/`OIHW2NK` 专用 `TLOAD` overload 也在该页面说明。 +- 需要将 CUBE comparison predicate 交给 scatter 或 masked atomic-add 时,阅读 + [实验性 PredicateCell adapters](predicate-adapters.md)。该接口仍在开发分支, + 需要配套 compiler builtin,不代表当前发布工具链已经支持。 - 常规逐元素算子从 [TADD](elementwise-tile-tile/arithmetic/TADD.md) 开始;按目录选择 算术、逻辑、转换、归约、布局和不规则操作。 - PTO ISA v0.58.7 的 CUBE layout 重排操作从 diff --git a/docs/tileop-usage/element-if.md b/docs/tileop-usage/element-if.md index a124bff9..52afc62c 100644 --- a/docs/tileop-usage/element-if.md +++ b/docs/tileop-usage/element-if.md @@ -83,6 +83,37 @@ void scalar_style(tile &out, const tile &lhs, const tile &rhs) { if (ready) { } // 错误:下一条语句不是 for ``` +### 1.3 与显式 PredicateCell API 的区别 + +Element-if 是编译器驱动的路径:开发者在 pragma 循环内写普通条件表达式, +由编译器构造、组合 predicate 并选择受支持的 masked 指令。通常不需要手工 +创建或传递 predicate carrier。 + +开发分支另有独立的、尚未发布的 +[PredicateCell adapters](predicate-adapters.md),供显式 TileOP 数据流使用: + +```cpp +auto active = predicate::compare_scalar(values, threshold); +predicate::scatter(destination, byte_offsets, values, active); +predicate::atomic_add(destination, byte_offsets, values, active); +``` + +这条路径不要求 `#pragma pto element for`、`#pragma linx elementwise` 或 +`TPARTELEMENT`,也不是供 pragma loop body 调用的入口。目前没有将 +`Predicate` 导入/导出 element-if 隐式 mask 的桥接接口,不能假设 +把这些调用放进循环就能编译。 + +两条路径使用相同架构中的 predicate/ExecutionMask 概念,但公开支持边界 +不同。显式 adapters 首版只覆盖 Local VEC CUBE_M32、静态有效区域和 +FP32/S32/U32,尚无 predicate 逻辑组合;本页 element-if 已支持的组合条件 +不能据此推广到 `Predicate`。显式 `atomic_add` 使用 MSCATTER_ADD +ExecutionMask,本页 histogram 示例的已验收 profile 则使用 MGATHER.ADD, +不能互相替代其 dtype、memory-order、返回值或验证结论。 + +写普通逐元素条件时优先使用本页的 element-if;已有显式 Tile comparison、 +需要把结果直接交给 scatter/atomic consumer 时,再选择实验性 adapters, +并满足其配套 compiler builtin 和独立类型约束。 + ## 2. 基本语法规则 推荐使用编译器容易证明的 canonical loop: diff --git a/docs/tileop-usage/options.md b/docs/tileop-usage/options.md index 1374fcba..b977ec12 100644 --- a/docs/tileop-usage/options.md +++ b/docs/tileop-usage/options.md @@ -13,6 +13,14 @@ 的 packed / one-column source 不能混用,编译期诊断见 [TCMP 约束](elementwise-tile-tile/logical/TCMP.md)。 +## 实验性 PredicateCell adapters + +`predicate::compare_scalar` 的强类型结果可以直接供给 +`predicate::scatter`,或作为 `predicate::atomic_add` 的 +ExecutionMask。`Invert` 是编译期布尔参数,默认 false,不属于 +`fixp::Options`。当前支持边界、byte-offset 单位和未支持的逻辑组合见 +[PredicateCell adapters](predicate-adapters.md)。 + ## 1. 快速开始 普通计算直接使用基础重载;需要后处理时,在对应的 `TMATMUL*` 或 `TGEMV*` 调用中传入 diff --git a/docs/tileop-usage/predicate-adapters.md b/docs/tileop-usage/predicate-adapters.md new file mode 100644 index 00000000..d3aac40b --- /dev/null +++ b/docs/tileop-usage/predicate-adapters.md @@ -0,0 +1,97 @@ +# Typed PredicateCell adapters (experimental) + +## Availability + +This is an explicit TileOP API, **not an element-if-only interface**. It does +not require a pragma loop or `TPARTELEMENT`. For scalar-style conditionals in +element loops, use the [Element-if guide](element-if.md); that path constructs +predicates internally. There is currently no public bridge between its implicit +masks and `Predicate`, and these adapter calls are not supported loop-body +operations. Sharing ISA predicate concepts does not make the two APIs or their +supported profiles interchangeable. + +This is an **unreleased development-branch interface**, not a claim about an +installed SDK. It requires the matching LLVM compiler changes that introduce +`ew_mscatter_add_predicate`; existing MC ExecutionMask support alone is not +sufficient. Keep the new TileOP headers and matching compiler together. Do not +replace headers in an older installed SDK before that compiler dependency is +available. + +Include `common/pto_tileop.hpp` when targeting Linx. The experimental +`pto::predicate` API keeps a comparison result distinct from ordinary numeric +Tiles and packed GPR predicates: + +```cpp +auto active = pto::predicate::compare_scalar(values, threshold); +pto::predicate::scatter(destination, byte_offsets, values, active); +pto::predicate::atomic_add(destination, byte_offsets, values, active); +pto::predicate::atomic_add(destination, byte_offsets, values, active); +``` + +## Complete compile-only example + +```cpp +#include + +using namespace pto; +using Values = VecTileM32; +using Offsets = VecTileM32; +using Global = global_tensor>; + +void predicate_consumers(Values &values, Offsets &byte_offsets, + Global &destination, uint32_t threshold) { + auto active = predicate::compare_scalar(values, threshold); + predicate::scatter(destination, byte_offsets, values, active); + predicate::atomic_add(destination, byte_offsets, values, active); + predicate::atomic_add(destination, byte_offsets, values, active); +} +``` + +The caller must supply initialized Numeric CUBE operands. Offsets are already +byte displacements, not element indices; this API does not scale them by the +value element size. This example demonstrates compilation only, not a complete +kernel initialization sequence or a model execution test. + +With the matching compiler, use the usual Linx target options and your SDK +sysroot/resource configuration: + +```bash +clang++ --target=linx64v5-unknown-linux-musl -mlxbc -fenable-matrix \ + -O2 -std=c++20 -D__linx -DENABLE_TENSOR_INSTR \ + --sysroot="$LINX_SYSROOT" -I"$TILEOP_ROOT/include" \ + -c predicate_consumers.cpp -o predicate_consumers.o +``` + +Ensure the compiler resource directory does not contain a stale TileOP include +tree that takes precedence over the selected headers. + +## Carrier and consumer contract + +The first implementation admits static valid shapes, Local VEC CUBE_M32, +FP32/S32/U32 comparison and value operands, and S64/U64 **byte displacements**. +The value type, valid region and layout must match the comparison basis. +Offsets may have a different physical capacity, but must cover that valid +region. `destination` must have the same element type as `values`. + +`scatter` binds the PredicateCell directly to the explicit MSCATTER_MASK +operand. It does not convert it with TCVT or reinterpret it as an ordinary +numeric U8 Tile. This follows the executable +`IndexedTLSUPredicateDescriptorLegal` helper; the instruction-page wording +discrepancy is tracked in PTO-ISA/pto-spec #349. + +`atomic_add` requires the compiler builtin `ew_mscatter_add_predicate` and +emits MSCATTER_ADD with a PredicateCell ExecutionMask. `true` inverts the +execution mask; this is not a new operation-owned atomic mask operand. +According to `GM_RED_VALUE`, inactive coordinates do not calculate indexed +addresses, probe memory or enqueue atomic RMWs. Structural legality checks +still apply. Replacing inactive addends with zero is not equivalent. + +`Predicate` cannot be default-constructed by callers or imported from +an arbitrary U8 Tile. Its carrier is intentionally private. Ordinary TAND is +not a PredicateCell logical operator. Predicate composition, GPR selection, +first-set-bit operations, dynamic valid shapes, M16 and cross-call predicate +ABI/spill support are not provided by this experimental interface. Keep +producer and consumers together in an optimized, inlined region. + +Object/assembly compilation checks are not model execution or TopK numerical +validation. No ISA or model legality checks are relaxed by this interface. diff --git a/docs/tileop-usage/support-status.md b/docs/tileop-usage/support-status.md index 403e1bb3..f64c84fa 100644 --- a/docs/tileop-usage/support-status.md +++ b/docs/tileop-usage/support-status.md @@ -28,6 +28,19 @@ rollback、definedness 或 memory-order 语义。 ## 版本边界 +### 开发分支中的 PredicateCell adapters(未发布) + +`predicate::compare_scalar`、`predicate::scatter` 和 +`predicate::atomic_add` 的首版封装仅覆盖 Local VEC CUBE_M32、 +静态有效区域、FP32/S32/U32 和 S64/U64 byte offsets。 +atomic-add 依赖配套 LLVM 的 `ew_mscatter_add_predicate` builtin; +不能由 MC 已支持 ExecutionMask 推断已安装 compiler 也支持该 API。 +目前验证包含对象生成、汇编 roundtrip 与负例,不包含完整 TopK 模型数值验收。 +Predicate 逻辑组合、GPR 自动适配、M16 和跨调用/spill 不在首版支持范围。 +示例、语义及依赖见 [PredicateCell adapters](predicate-adapters.md)。 + +### 已发布基线 + - 当前 API/spec 基线:PTO ISA `0.58.7`,publication `0.58.7.0`,ABI `pto-isa-0.58.7-mode-function-v1`。 - backend 能力必须分别判断:CPU simulator/JCORE wrapper、LLVM MC @@ -39,4 +52,4 @@ rollback、definedness 或 memory-order 语义。 0.58.3 compiler、ELF identity 和旧 engine projection 不表示当前实现基线。 - 需要核对精确 fault ordering、completion、rollback、memory ordering 或编码位域时, 必须回到 `PTO-ISA/pto-spec` 对应 ASL/NDF owner;Markdown wrapper 文档不构成完整 - 规范镜像。 \ No newline at end of file + 规范镜像。 diff --git a/docs/tileop-usage/tile-scalar-and-immediate/logical/TCMPS.md b/docs/tileop-usage/tile-scalar-and-immediate/logical/TCMPS.md index 5d43b441..919b3f49 100644 --- a/docs/tileop-usage/tile-scalar-and-immediate/logical/TCMPS.md +++ b/docs/tileop-usage/tile-scalar-and-immediate/logical/TCMPS.md @@ -2,6 +2,14 @@ `TCMPS` 按照 CMode 将每个有效 Tile 元素与每 PE 标量比较,并将谓词结果编码为 0 或 1。 +### 实验性强类型生产者 + +开发分支新增 `predicate::compare_scalar(source, scalar)`,返回私有 +carrier 的 `Predicate`,避免将 CUBE PredicateCell、普通 Numeric U8 +和 packed GPR 混为同一类型。它是独立封装,不改变本页既有 `TCMPS` 重载。 +直接 scatter、ExecutionMask atomic-add 及当前支持边界见 +[PredicateCell adapters](../../predicate-adapters.md)。该接口尚未发布。 + ## C++ 接口 当前 API 中可用的调用形式: diff --git a/docs/tileop-usage/tlsu/memory/MSCATTER_ADD.md b/docs/tileop-usage/tlsu/memory/MSCATTER_ADD.md index dc001bda..5fdf1a93 100644 --- a/docs/tileop-usage/tlsu/memory/MSCATTER_ADD.md +++ b/docs/tileop-usage/tlsu/memory/MSCATTER_ADD.md @@ -4,6 +4,15 @@ ## C++ 接口 +### 实验性 ExecutionMask 消费者 + +开发分支提供 `predicate::atomic_add(destination, byte_offsets, values, active)`。 +它通过配套 compiler builtin 生成 PredicateCell ExecutionMask,而非为原始 +MSCATTER_ADD 增加普通 source 操作数。`Invert` 默认为 false;inactive lane +不计算索引地址、不 probe、不发起 atomic RMW,结构合法性检查仍保留。 +零 addend 不等价于屏蔽。该接口尚未发布,不改变下列既有重载;详见 +[PredicateCell adapters](../../predicate-adapters.md)。 + 当前 API 中可用的调用形式: ```cpp diff --git a/docs/tileop-usage/tlsu/memory/MSCATTER_MASK.md b/docs/tileop-usage/tlsu/memory/MSCATTER_MASK.md index 4730d41f..333bf97e 100644 --- a/docs/tileop-usage/tlsu/memory/MSCATTER_MASK.md +++ b/docs/tileop-usage/tlsu/memory/MSCATTER_MASK.md @@ -4,6 +4,15 @@ ## C++ 接口 +### 实验性 PredicateCell 消费者 + +开发分支提供 `predicate::scatter(destination, byte_offsets, values, active)`, +可直接消费 `predicate::compare_scalar` 返回的强类型 CUBE PredicateCell, +不插入 TCVT。它不是把普通 U8 Tile 强制转换成 PredicateCell,也不改变 +下列既有重载。当前 executable ASL helper 接受这一 carrier,但指令页文字 +仍有待 owner 对齐的差异;支持边界与说明见 +[PredicateCell adapters](../../predicate-adapters.md)。该接口尚未发布。 + 当前 API 中可用的调用形式: ```cpp @@ -125,4 +134,4 @@ void scatter_masked(float *base, const float *input, const uint32_t *offsets, } ``` -涉及标量、索引、scale 或 bias 的操作,请按上方实际重载替换示例参数。 \ No newline at end of file +涉及标量、索引、scale 或 bias 的操作,请按上方实际重载替换示例参数。 diff --git a/include/common/pto_predicate.hpp b/include/common/pto_predicate.hpp new file mode 100644 index 00000000..0d450d24 --- /dev/null +++ b/include/common/pto_predicate.hpp @@ -0,0 +1,148 @@ +#ifndef PTO_PREDICATE_HPP +#define PTO_PREDICATE_HPP + +#include "common/pto_tile.hpp" + +namespace pto { +namespace predicate { + +template class Predicate; +template +__attribute__((always_inline)) inline Predicate +compare_scalar(Basis &source, typename Basis::DType scalar); + +template class Predicate { + static_assert(Basis::Loc == Location::Vec && + Basis::BFractal == BLayout::CubeM32, + "Predicate currently requires a Local VEC CUBE_M32 basis"); + static_assert(Basis::ValidRow > 0 && Basis::ValidCol > 0, + "Predicate currently requires static valid dimensions"); + static constexpr int StorageRows = 32; + static constexpr int MinimumColumns = 128 / StorageRows; + static constexpr int StorageColumns = Basis::Cols < MinimumColumns + ? MinimumColumns : Basis::Cols; + using Storage = Tile; + typename Storage::TileRegisterType Carrier; + + Predicate() = default; + template + friend __attribute__((always_inline)) inline Predicate + compare_scalar(Source &, typename Source::DType); + template + friend __attribute__((always_inline)) inline void + scatter(Global &, const Index &, const Value &, const Predicate &); + template + friend __attribute__((always_inline)) inline void + atomic_add(Global &, const Index &, const Value &, const Predicate &); + +public: + using BasisTile = Basis; +}; + +template +__attribute__((always_inline)) inline Predicate +compare_scalar(Basis &source, typename Basis::DType scalar) { + static_assert(Mode == CmpMode::EQ || Mode == CmpMode::NE || + Mode == CmpMode::LT || Mode == CmpMode::GT || + Mode == CmpMode::LE || Mode == CmpMode::GE, + "Predicate comparison mode is reserved"); + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v, + "Predicate comparison currently supports FP32/S32/U32"); + Predicate result; + asm volatile( + "BSTART.TEPL 45, %D[Type]\n" + ".if %c[Mode] == 0\nB.DATR CUBE_M32, DTYPE_NONE, Zero, EQ, RNONE\n" + ".elseif %c[Mode] == 1\nB.DATR CUBE_M32, DTYPE_NONE, Zero, NE, RNONE\n" + ".elseif %c[Mode] == 2\nB.DATR CUBE_M32, DTYPE_NONE, Zero, LT, RNONE\n" + ".elseif %c[Mode] == 3\nB.DATR CUBE_M32, DTYPE_NONE, Zero, GT, RNONE\n" + ".elseif %c[Mode] == 4\nB.DATR CUBE_M32, DTYPE_NONE, Zero, LE, RNONE\n" + ".else\nB.DATR CUBE_M32, DTYPE_NONE, Zero, GE, RNONE\n.endif\n" + "B.DIM zero, %c[Columns], ->LB0\n" + "B.DIM zero, %c[Rows], ->LB1\n" + "B.DIM zero, %c[PhysicalColumns], ->LB2\n" + "B.IOT %[Source], mask=1111, last, ->%[Result]<%Z[Size]>\n" + "B.IOR [%[Scalar]], []\n" + : [Result] "=Tr"(result.Carrier) + : [Source] "Tr"(source.data()), [Scalar] "r"(scalar), + [Type] "i"(type_traits::TypeCode), + [Mode] "i"(static_cast(Mode)), + [Rows] "i"(Basis::ValidRow), [Columns] "i"(Basis::ValidCol), + [PhysicalColumns] "i"(Basis::Cols), + [Size] "i"(tile_type_traits::Storage::TileDType>::TilesizeCode)); + return result; +} + +template +constexpr void validate_consumer() { + static_assert(Index::Loc == Location::Vec && Value::Loc == Location::Vec, + "Predicate consumers require Local VEC operands"); + static_assert(Index::BFractal == Basis::BFractal && + Value::BFractal == Basis::BFractal, + "Predicate consumer layout must match producer layout"); + static_assert(Index::ValidRow == Basis::ValidRow && + Index::ValidCol == Basis::ValidCol && + Value::ValidRow == Basis::ValidRow && + Value::ValidCol == Basis::ValidCol, + "Predicate consumer valid shape must match producer shape"); + static_assert(std::is_same_v || + std::is_same_v, + "Predicate indices must be S64/U64 byte displacements"); + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v, + "Predicate consumers currently support FP32/S32/U32 values"); + static_assert(std::is_same_v, + "Predicate consumer data type must match its comparison basis"); + static_assert(Index::Cols >= Basis::ValidCol && + Value::Cols >= Basis::ValidCol, + "Predicate operands must cover the complete valid region"); +} + +template +__attribute__((always_inline)) inline void +scatter(Global &destination, const Index &offsets, const Value &values, + const Predicate &active) { + validate_consumer(); + static_assert(std::is_same_v, + "Predicate destination element type must match values"); + asm volatile( + "BSTART.TLSU MSCATTER.MASK, %D[Type]\n" + "B.DATR CUBE_M32, Zero\n" + "B.DIM zero, %c[Columns], ->LB0\n" + "B.DIM zero, %c[Rows], ->LB1\n" + "B.DIM zero, %c[PhysicalColumns], ->LB2\n" + "B.IOT %[Values], %[Offsets], mask=1111\n" + "B.IOT %[Predicate], mask=1111, last\n" + "B.IOR [%[Base]], []\n" + : + : [Values] "Tr"(values.data()), [Offsets] "Tr"(offsets.data()), + [Predicate] "Tr"(active.Carrier), [Base] "r"(destination.data()), + [Type] "i"(type_traits::TypeCode), + [Columns] "i"(Basis::ValidCol), [Rows] "i"(Basis::ValidRow), + [PhysicalColumns] "i"(Value::Cols) + : "memory"); +} + +template +__attribute__((always_inline)) inline void +atomic_add(Global &destination, const Index &offsets, const Value &values, + const Predicate &active) { + validate_consumer(); + static_assert(std::is_same_v, + "Predicate destination element type must match values"); + ew_mscatter_add_predicate( + Basis::ValidRow, Basis::ValidCol, Value::Cols, + type_traits::TypeCode, + 29, destination.data(), + values.data(), offsets.data(), active.Carrier, Invert); +} + +} +} + +#endif diff --git a/include/common/pto_tileop.hpp b/include/common/pto_tileop.hpp index 1c221a9f..7aa5b544 100644 --- a/include/common/pto_tileop.hpp +++ b/include/common/pto_tileop.hpp @@ -11,6 +11,7 @@ #ifdef __linx #include "common/pto_tile_region_inline_asm.hpp" +#include "common/pto_predicate.hpp" #endif // Returns the current PE ID (0..3). diff --git a/test/test_predicate_adapters.py b/test/test_predicate_adapters.py new file mode 100644 index 00000000..33591089 --- /dev/null +++ b/test/test_predicate_adapters.py @@ -0,0 +1,101 @@ +import os +import shutil +import subprocess +import tempfile +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +CXX = Path(os.environ.get("LINX_CXX", "/home/zhuwei/linx-llvm/build/bin/clang++")) +SYSROOT = Path(os.environ.get( + "LINX_SYSROOT", + "/home/zhuwei/linx-toolchain-build-online-main/output/linx_blockisa_llvm_musl/sysroot", +)) + + +class PredicateAdaptersTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + if not CXX.is_file() or not SYSROOT.is_dir(): + raise unittest.SkipTest("Linx compiler/sysroot unavailable") + cls.temporary = tempfile.TemporaryDirectory() + cls.addClassCleanup(cls.temporary.cleanup) + cls.output = Path(cls.temporary.name) + resource = cls.output / "resource" + original = subprocess.check_output([str(CXX), "-print-resource-dir"], text=True) + shutil.copytree(original.strip(), resource) + shutil.rmtree(resource / "include/tileop-api", ignore_errors=True) + shutil.copytree(ROOT / "include", resource / "include/tileop-api") + cls.flags = [ + str(CXX), "-resource-dir", str(resource), + "--target=linx64v5-unknown-linux-musl", "-mlxbc", "-fenable-matrix", + "-O2", "-std=c++20", "-D__linx", "-DENABLE_TENSOR_INSTR", + "--sysroot", str(SYSROOT), "-nostdinc++", + "-isystem", str(SYSROOT / "usr/include/c++/v1"), + ] + + def compile(self, body, *arguments, dtype="uint32_t", columns=4): + source = self.output / "predicate.cpp" + source.write_text( + '#include \nusing namespace pto;\n' + f'using Values = VecTileM32<{dtype}, 32, {columns}>;\n' + f'using Offsets = VecTileM32;\n' + f'using Global = global_tensor<{dtype}, RowMajor<32, {columns}>>;\n' + f'void test(Values &values, Offsets &offsets, Global &destination, ' + f'{dtype} threshold) {{\n{body}\n}}\n' + ) + return subprocess.run(self.flags + list(arguments) + [str(source)], + text=True, capture_output=True) + + def test_object_and_execution_mask(self): + body = '''auto active = predicate::compare_scalar(values, threshold); +predicate::scatter(destination, offsets, values, active); +predicate::atomic_add(destination, offsets, values, active); +predicate::atomic_add(destination, offsets, values, active);''' + objdump = CXX.parent / "llvm-objdump" + for dtype in ("uint32_t", "int32_t", "float"): + for columns in (4, 8): + with self.subTest(dtype=dtype, columns=columns): + obj = self.output / "predicate.o" + result = self.compile(body, "-c", "-o", str(obj), + dtype=dtype, columns=columns) + self.assertEqual(result.returncode, 0, result.stderr) + text = subprocess.check_output([ + str(objdump), "-d", "--no-show-raw-insn", + "--disassembler-options=no-tile-macros", str(obj), + ], text=True) + self.assertEqual(text.count("ExecMaskPresent"), 2, text) + self.assertEqual(text.count("MSCATTER.ADD"), 2, text) + self.assertEqual(text.count("MSCATTER.MASK"), 1, text) + self.assertNotIn("TCVT", text) + self.assertNotIn("TLOAD, S64", text) + self.assertIn("ND2M32", text) + self.assertIn("nosat, 1, 0", text) + + def test_no_uninitialized_predicate(self): + result = self.compile("predicate::Predicate active;", "-fsyntax-only") + self.assertNotEqual(result.returncode, 0) + self.assertIn("private constructor", result.stderr) + + def test_numeric_tile_is_not_predicate(self): + result = self.compile( + "predicate::scatter(destination, offsets, values, values);", "-fsyntax-only") + self.assertNotEqual(result.returncode, 0) + self.assertIn("no matching function", result.stderr) + + def test_shape_mismatch(self): + result = self.compile('''auto active = predicate::compare_scalar(values, threshold); +VecTileM32 wrong; +predicate::scatter(destination, wrong, values, active);''', "-fsyntax-only") + self.assertNotEqual(result.returncode, 0) + self.assertIn("valid shape must match", result.stderr) + + def test_builtin_wrong_arity(self): + result = self.compile("ew_mscatter_add_predicate(1);", "-fsyntax-only") + self.assertNotEqual(result.returncode, 0) + self.assertIn("10", result.stderr) + + +if __name__ == "__main__": + unittest.main()