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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10,704 changes: 7,662 additions & 3,042 deletions include/jcore/template_asm.hpp

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions test/test_static_valid_shape_lowering.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import subprocess
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
HEADER = (ROOT / "include/jcore/template_asm.hpp").read_text(encoding="utf-8")
FIXTURE = (ROOT / "test/tileop_api/src/ValidShapeImmediate.cpp").read_text(encoding="utf-8")
LLVM_MC = Path("/home/zhuwei/linx-llvm/build/bin/llvm-mc")

class TestStaticValidShapeLowering(unittest.TestCase):
def test_shared_tload_uses_zero_in_dim_first_operand(self) -> None:
self.assertIn('"B.DIM zero, %c[VCOL], ->lb0\\n"', HEADER)
self.assertIn('"B.DIM zero, %c[VROW], ->lb1\\n"', HEADER)
self.assertIn('[VCOL]"i"(valid_col)', HEADER)
self.assertIn('[VROW]"i"(valid_row)', HEADER)
self.assertNotIn('[VCOL]"ri"(valid_col)', HEADER)
self.assertNotIn('[VROW]"ri"(valid_row)', HEADER)

def test_shared_tload_keeps_c_b_dimi_compression_coverage(self) -> None:
self.assertIn('"B.DIM zero, %c[COL], ->lb2\\n"', HEADER)
self.assertTrue(LLVM_MC.exists())
result = subprocess.run(
[str(LLVM_MC), "-triple=linx64v5", "-show-encoding"],
input=b"B.DIM zero, 16, ->lb0\n",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=True,
)
self.assertIn(b"C.B.DIMI", result.stdout)

def test_fixture_declares_static_and_dynamic_tiles(self) -> None:
self.assertIn('using S = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor>;', FIXTURE)
self.assertIn('using D = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor, -1, -1>;', FIXTURE)
self.assertIn('TLOAD(dst, src);', FIXTURE)

if __name__ == '__main__':
unittest.main()
85 changes: 53 additions & 32 deletions test/test_v058_engine_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def test_tcvt_emits_dimensions_before_terminating_iot(self) -> None:
tcvt = re.search(
r'(?s)template <is_tile_data_v tile_shape_out, '
r'is_tile_data_v tile_shape_in>\n'
r'void TCVT_T\(.*?\n}\n\n#define DEFINE_TMOV_LAYOUT',
r'void TCVT_T\(.*?\n}\n\n\n// PTO ISA 0.58 generic Local-to-Local TMOV',
self.header,
)
self.assertIsNotNone(tcvt)
Expand All @@ -147,8 +147,8 @@ def test_tcvt_emits_dimensions_before_terminating_iot(self) -> None:
for instruction in (
'"BSTART.TEPL 27, %D1\\n"',
'"B.DATR %D2, RNONE\\n"',
'"B.DIM %5, 0, ->lb0\\n"',
'"B.DIM %6, 0, ->lb1\\n"',
'"B.DIM zero, %c5, ->lb0\\n"',
'"B.DIM zero, %c6, ->lb1\\n"',
'"B.DIM zero, %c7, ->lb2\\n"',
'"B.IOT %3, mask=1111, last, ->%0<%Z4>\\n"'):
self.assertIn(instruction, ordinary_branch)
Expand All @@ -157,7 +157,7 @@ def test_tcvt_cube_layout_closure_uses_destination_tsize(self) -> None:
tcvt = re.search(
r'(?s)template <is_tile_data_v tile_shape_out, '
r'is_tile_data_v tile_shape_in>\n'
r'void TCVT_T\(.*?\n}\n\n#define DEFINE_TMOV_LAYOUT',
r'void TCVT_T\(.*?\n}\n\n\n// PTO ISA 0.58 generic Local-to-Local TMOV',
self.header,
)
self.assertIsNotNone(tcvt)
Expand Down Expand Up @@ -244,19 +244,23 @@ def test_tsel_binds_explicit_false_source_on_both_shape_paths(self) -> None:
start = self.header.index("void TSEL(")
end = self.header.index("// TABS:", start)
tsel = self.header[start:end]
# Per-dimension dispatch: four B.DIM combinations, each a full asm
# statement with the same binder shape.
self.assertEqual(
tsel.count('"B.IOT %6, %7, mask=1111\\n"'), 2
tsel.count('"B.IOT %6, %7, mask=1111\\n"'), 4
)
self.assertEqual(
tsel.count(
'"B.IOT %1, mask=1111, last, ->%0<%Z8>\\n"'
),
2,
4,
)
self.assertEqual(tsel.count(': [Dst] "=Tr"(dst.data())'), 2)
self.assertEqual(tsel.count('[Prior] "0"(dst.data())'), 2)
self.assertEqual(tsel.count(': [Dst] "=Tr"(dst.data())'), 4)
self.assertEqual(tsel.count('[Prior] "0"(dst.data())'), 4)
self.assertIn('"B.DIM zero, %c3, ->lb0\\n"', tsel)
self.assertIn('"B.DIM %3, 0, ->lb0\\n"', tsel)
self.assertIn('"B.DIM %[mask____dimcol], 0, ->lb0\\n"', tsel)
self.assertIn('"B.DIM %[mask____dimrow], 0, ->lb1\\n"', tsel)
self.assertIn("::ValidCol > 0 && ", tsel)
self.assertNotIn(
'"B.IOT %5, %6, mask=1111, last, ->%0<%Z7>\\n"', tsel
)
Expand All @@ -265,25 +269,17 @@ def test_valid_shape_immediate_fixture_separates_static_and_dynamic_paths(self)
fixture = (ROOT / "test" / "tileop_api" / "src" / "ValidShapeImmediate.cpp").read_text(
encoding="utf-8"
)
self.assertIn('"i" + C.B.DIMI path', fixture)
self.assertIn('"r" + B.DIM path', fixture)
self.assertIn('using S = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor>;', fixture)
self.assertIn('using D = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor, -1, -1>;', fixture)
self.assertIn('static void static_path(SR &dst, GM &src)', fixture)
self.assertIn('static void dynamic_path(DR &dst, GM &src)', fixture)

def test_static_valid_shape_bindings_remain_immediate_eligible(self) -> None:
header = self.header
timg2col = header[header.index("void TIMG2COL"):header.index("// TFILLPAD")]
for spelling in (
'"ri"(dst.GetValidCol())',
'"ri"(dst.GetValidRow())',
'"ri"(src.GetValidCol())',
'"ri"(src.GetValidRow())',
'"ri"(offset.GetValidCol())',
'"ri"(offset.GetValidRow())',
'[VCOL] "ri"(validCol)',
'[VROW] "ri"(validRow)',
):
self.assertIn(spelling, header)
# Per-dimension dispatch: every B.DIM site splits into the four
# ValidCol/ValidRow combinations, so no "ri" masquerade may remain
# and fully-dynamic operands must bind as plain "r" registers.
self.assertNotIn('"ri"(', header)
for spelling in (
'"r"(dst.GetValidCol())',
'"r"(dst.GetValidRow())',
Expand All @@ -292,9 +288,28 @@ def test_static_valid_shape_bindings_remain_immediate_eligible(self) -> None:
'"r"(offset.GetValidCol())',
'"r"(offset.GetValidRow())',
):
self.assertNotIn(spelling, header[: header.index("void TIMG2COL")])
self.assertIn('"r"(dst.GetValidCol())', timg2col)
self.assertIn('"r"(dst.GetValidRow())', timg2col)
self.assertIn(spelling, header)
# A register must never land in the immediate slot of B.DIM.
self.assertNotIn("B.DIM zero, %[", header)
# lb2 must always stay the static immediate form; the only allowed
# dynamic lb2 is TPREFETCH's dynamic-GM fallback branch.
dyn_lb2 = [
line for line in header.split("\n")
if "->lb2" in line and "B.DIM" in line and "%c" not in line
]
tprefetch = header[header.index("void TPREFETCH"):]
tprefetch = tprefetch[: tprefetch.index("\n}")]
self.assertEqual(
len(dyn_lb2),
tprefetch.count('"B.DIM %[Col], 0, ->lb2\\n"'),
"lb2 must be immediate-form everywhere except TPREFETCH's "
"dynamic-GM fallback",
)
# TMATMUL: M stays a runtime register (group_M), N/K are compile-time
# immediates per resolve_matmul_shape.
self.assertIn('"B.DIM %[M], 0, ->lb0\\n"', header)
self.assertIn('"B.DIM zero, %c[N], ->lb1\\n"', header)
self.assertIn('"B.DIM zero, %c[K], ->lb2\\n"', header)

def test_fpatr_carries_shared_transpose_controls(self) -> None:
tile = PTO_TILE.read_text(encoding="utf-8")
Expand Down Expand Up @@ -533,12 +548,18 @@ def test_timg2col_bundle_has_gm_and_parameter_iors(self) -> None:
self.assertRegex(self.header, r'B\.IOT mask=1111, last, ->%\[Dst\]')

def test_timg2col_uses_destination_geometry_and_cube_output(self) -> None:
body = self.header[self.header.index("// TIMG2COL") : self.header.index("// TFILLPAD")]
self.assertIn("tile_shape_out::Loc == Location::Left", body)
self.assertIn('"r"(dst.GetValidCol())', body)
self.assertIn('"r"(dst.GetValidRow())', body)
self.assertIn("BLayout::CubeM16", body)
self.assertIn("BLayout::CubeM32", body)
body = self.header[self.header.index("void TIMG2COL"):self.header.index("// TFILLPAD")]
self.assertIn(
"requires(tile_shape_out::Loc == Location::Left",
self.header[self.header.rindex("template", 0, self.header.index("void TIMG2COL")):self.header.index("// TFILLPAD")])
self.assertIn('[ValidCol] "r"(dst.GetValidCol())', body)
self.assertIn('[ValidRow] "r"(dst.GetValidRow())', body)
self.assertIn('[ValidCol] "i"(tile_shape_out::ValidCol)', body)
self.assertIn('[ValidRow] "i"(tile_shape_out::ValidRow)', body)
self.assertIn('"B.DIM zero, %c[ValidCol], ->lb0\\n"', body)
self.assertIn('"B.DIM zero, %c[ValidRow], ->lb1\\n"', body)
self.assertIn("LayoutCvtEnum::ND2M16", body)
self.assertIn("LayoutCvtEnum::ND2M32", body)

def test_tquant_tdequant_use_datr_and_ior(self) -> None:
# TQUANT/TDEQUANT: B.DATR carries named dtype/RMode and optional sat,
Expand Down
2 changes: 1 addition & 1 deletion test/tileop_api/compile.all
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ ACTIVE_FIXTURES=(
TAdd_mask TAdd TAdds TAnd TLoad TStore TCvt TDiv TDivs TExp TMax TMaxs
TMov TMul TMuls TOr TRecip TRem TRowMax TRowSum TSqrt TSub TSubs TTrans
RangeSubview RangeAssemble SharedRange TileRegion TileArray TileArrayCube TileArrayAssemblyOffsets TileArrayRegionAsm TileRegionUnaryAssembly TileRegionUnary TileRegionBinary TileRegionBinaryAssembly TileRegionUnarySubviewAssembly TileRegionScalarAssembly TileRegionTCVTSubviewAssembly TileRegionSharedSubview TileRegionCubeSubview
ValidShapeImmediate TSELCanonical
ValidShapeImmediate ValidShapePerDim ValidShapePerDimSweep TSELCanonical
)

# Pre-v0.58 wrapper designs remain available to cpu_sim only and are not part
Expand Down
139 changes: 10 additions & 129 deletions test/tileop_api/src/ValidShapeImmediate.cpp
Original file line number Diff line number Diff line change
@@ -1,136 +1,17 @@
// Regression coverage for static/dynamic valid-shape immediate lowering.
//
// The 2026-09-01 valid-shape batch converted ordinary TileOP inline asm so
// that a statically-typed Tile (ValidRow/ValidCol > 0) binds LB0/LB1 as "i"
// immediates (C.B.DIMI eligible), while dynamic Tiles (ValidRow/ValidCol ==
// -1) keep the "r" register form. This fixture instantiates every converted
// family on both path kinds so template-instantiation errors, wrong-template
// name typos (e.g. the original TCONCAT "tile_shape" bug), and operand
// misbindings are caught by the compile gate.
//
// It is a compile-only regression: main() is minimal. The objective gate is
// `make TESTCASE=ValidShapeImmediate object` under the matching Linx
// toolchain; locally it must at least pass -fsyntax-only on both paths.
#include <jcore/template_asm.hpp>

#include <common/pto_tileop.hpp>

using namespace pto;

// Static half: a fully-static Tile drives the "i" + C.B.DIMI path.
using S = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor>;
using SR = Tile<Location::Vec, float, 16, 1, BLayout::RowMajor>; // row-reduce dst / row-expand source
using SC = Tile<Location::Vec, float, 1, 16, BLayout::RowMajor>; // col-reduce dst / col-expand source
using SE = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor>; // expand destination
using SO = Tile<Location::Vec, uint16_t, 16, 16, BLayout::RowMajor>;
using D = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor, -1, -1>;
using SR = SharedTile<S>;
using DR = SharedTile<D>;
using GM = Global<float>;

__attribute__((noinline)) void static_path(S &d, S &a, S &b, S &c,
SR &r, SC &rc, SE &ex,
SO &off, float v) {
// elementwise batch
TADD(d, a, b);
TSUB(d, a, b);
TMUL(d, a, b);
TDIV(d, a, b);
TREM(d, a, b);
TAND(d, a, b);
TOR(d, a, b);
TXOR(d, a, b);
TSHL(d, a, b);
TSHR(d, a, b);
TMAX(d, a, b);
TMIN(d, a, b);
// reduce / broadcast batch
TROWSUM(r, a);
TROWMAX(r, a);
TROWMIN(r, a);
TROWARGMAX(r, a);
TROWEXPAND(ex, r);
TCOLSUM(rc, a);
TCOLEXPAND(ex, rc);
TROWEXPANDADD(ex, d, r);
TROWEXPANDSUB(ex, d, r);
TROWEXPANDMUL(ex, d, r);
// unary / scalar batch
TABS(d, a);
TNOT(d, a);
TNEG(d, a);
TEXP(d, a);
TLOG(d, a);
TSQRT(d, a);
TRSQRT(d, a);
TRELU(d, a);
TSUBS(d, a, v);
TDIVS(d, a, v);
TREMS(d, a, v);
TANDS(d, a, v);
TORS(d, a, v);
TXORS(d, a, v);
TSHLS(d, a, v);
TSHRS(d, a, v);
TMAXS(d, a, v);
TMINS(d, a, v);
TEXPANDS(d, v);
TFMA(d, a, b, c);
// part / gather batch
TSEL(d, a, b);
TPARTADD(d, a, b);
TPARTMUL(d, a, b);
TPARTMAX(d, a, b);
TPARTMIN(d, a, b);
TGATHER(d, a, off);
TSCATTER(a, d, off);
// movement / concat batch (TCONCAT regression: dst-derived valid shape)
TEXTRACT(c, a, 1, 2);
TINSERT(c, a, 1, 2);
TTRANS(c, a);
// TCONCAT requires src0/src1/dst to share dtype and row count; the valid
// shape used by its B.DIM is dst-derived (tile_shape_out), which regresses
// the original "tile_shape" undeclared-identifier bug.
TCONCAT(d, a, b);
static void static_path(SR &dst, GM &src) {
TLOAD(dst, src);
}

// Dynamic half: same families on dynamic Tiles drive the "r" + B.DIM path.
using D = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor, -1, -1>;
using DR = Tile<Location::Vec, float, 16, 1, BLayout::RowMajor, -1, -1>; // row-reduce dst / row-expand source
using DC = Tile<Location::Vec, float, 1, 16, BLayout::RowMajor, -1, -1>; // col-reduce dst / col-expand source
using DE = Tile<Location::Vec, float, 16, 16, BLayout::RowMajor, -1, -1>; // expand destination
using DO = Tile<Location::Vec, uint16_t, 16, 16, BLayout::RowMajor, -1, -1>;

__attribute__((noinline)) void dynamic_path(D &d, D &a, D &b, D &c,
DR &r, DC &rc, DE &ex,
DO &off, float v) {
TADD(d, a, b);
TSUB(d, a, b);
TMUL(d, a, b);
TROWSUM(r, a);
TROWMAX(r, a);
TROWEXPAND(ex, r);
TROWEXPANDADD(ex, d, r);
TABS(d, a);
TEXP(d, a);
TSUBS(d, a, v);
TMAXS(d, a, v);
TSEL(d, a, b);
TPARTMUL(d, a, b);
TGATHER(d, a, off);
TSCATTER(a, d, off);
TEXTRACT(c, a, 1, 2);
TTRANS(c, a);
TCONCAT(d, a, b);
static void dynamic_path(DR &dst, GM &src) {
TLOAD(dst, src);
}

int main() {
S s, sa, sb, sc;
SR sr;
SC src;
SE sex;
SO soff;
D d, da, db, dc;
DR dr;
DC drc;
DE dex;
DO doff;
static_path(s, sa, sb, sc, sr, src, sex, soff, 1.0f);
dynamic_path(d, da, db, dc, dr, drc, dex, doff, 1.0f);
return 0;
}
int main() { return 0; }
45 changes: 45 additions & 0 deletions test/tileop_api/src/ValidShapePerDim.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Per-dimension static/dynamic B.DIM regression: all four ValidCol/ValidRow
// combinations must lower to the correct B.DIM form.
// static dim -> B.DIM zero, imm (assembler compresses to C.B.DIMI)
// dynamic dim -> B.DIM reg, 0
#include <jcore/template_asm.hpp>

using namespace pto;

// SS: both dims static
using TSS = Tile<Location::Vec, float, 32, 32, BLayout::RowMajor, 16, 32>;
// SD: static col, dynamic row
using TSD = Tile<Location::Vec, float, 32, 32, BLayout::RowMajor, 16, -1>;
// DS: dynamic col, static row
using TDS = Tile<Location::Vec, float, 32, 32, BLayout::RowMajor, -1, 32>;
// DD: both dynamic
using TDD = Tile<Location::Vec, float, 32, 32, BLayout::RowMajor, -1, -1>;

__attribute__((noinline)) void case_ss_static_both(TSS &a, TSS &b, TSS &c) { TADD(a, b, c); }
__attribute__((noinline)) void case_sd_static_col(TSD &a, TSD &b) { TADD(a, b, b); }
__attribute__((noinline)) void case_ds_static_row(TDS &a, TDS &b) { TADD(a, b, b); }
__attribute__((noinline)) void case_dd_dynamic_both(TDD &a, TDD &b) { TADD(a, b, b); }

__attribute__((noinline)) void case_tmul_sd_ds(TSD &a, TSD &b, TDS &c, TDS &d) {
TMUL(a, b, b);
TMUL(c, d, d);
}

__attribute__((noinline)) void case_trecip_dynamic(TDD &a, TDD &b) { TRECIP(a, b); }

__attribute__((noinline)) void case_texp_mixed(TSD &a, TSD &b) { TEXP(a, b); }

int main() {
static TSS ss_a, ss_b, ss_c;
static TSD sd_a, sd_b;
static TDS ds_a, ds_b;
static TDD dd_a, dd_b;
case_ss_static_both(ss_a, ss_b, ss_c);
case_sd_static_col(sd_a, sd_b);
case_ds_static_row(ds_a, ds_b);
case_dd_dynamic_both(dd_a, dd_b);
case_tmul_sd_ds(sd_a, sd_b, ds_a, ds_b);
case_trecip_dynamic(dd_a, dd_b);
case_texp_mixed(sd_a, sd_b);
return 0;
}
Loading
Loading