@@ -76,6 +76,7 @@ def _handle_buffer_region_2d(br: BufferRegion, mask):
7676
7777 Leading dimensions are folded into rows; the innermost dimension is kept as cols.
7878 """
79+ _validate_buffer_region_outer_contiguity (br )
7980 bf = br .buffer
8081 indices = [x .min for x in br .region ]
8182 offset = bf .offset_of (indices )[0 ]
@@ -2300,6 +2301,49 @@ def _shapes_equal(shape1, shape2) -> bool:
23002301 return all (_const_equal (x , y ) for x , y in zip (shape1 , shape2 ))
23012302
23022303
2304+ def _fold_nd_shape_to_2d (shape ):
2305+ """Fold all leading dimensions of a shape into a single row dimension."""
2306+ shape = list (shape )
2307+ if len (shape ) <= 1 :
2308+ return shape
2309+ return [math .prod (shape [:- 1 ]), shape [- 1 ]]
2310+
2311+
2312+ def _validate_buffer_region_outer_contiguity (br : BufferRegion ) -> None :
2313+ """Validate that folding a region's outer dimensions does not cross gaps.
2314+
2315+ The final dimension is a row window and may be narrower than the physical
2316+ buffer because row-expand codegen retains the physical row stride. Once an
2317+ outer dimension spans multiple entries, however, every following outer
2318+ dimension must be selected in full.
2319+ """
2320+ spans_multiple_entries = False
2321+ for axis , region in enumerate (br .region [:- 1 ]):
2322+ if spans_multiple_entries :
2323+ starts_at_zero = _const_equal (region .min , 0 )
2324+ has_full_extent = _const_equal (region .extent , br .buffer .shape [axis ])
2325+ full_axis = starts_at_zero and has_full_extent
2326+ if not full_axis :
2327+ requirement = "BufferRegion outer dimensions must be contiguous"
2328+ raise ValueError (f"{ requirement } ; axis { axis } is not selected in full." )
2329+ elif not _is_const_one (region .extent ):
2330+ spans_multiple_entries = True
2331+
2332+
2333+ def _validate_buffer_region_flat_contiguity (br : BufferRegion ) -> None :
2334+ """Validate that a region can be consumed as one flat contiguous stream."""
2335+ spans_multiple_entries = False
2336+ for axis , region in enumerate (br .region ):
2337+ if spans_multiple_entries :
2338+ starts_at_zero = _const_equal (region .min , 0 )
2339+ has_full_extent = _const_equal (region .extent , br .buffer .shape [axis ])
2340+ if not (starts_at_zero and has_full_extent ):
2341+ requirement = "BufferRegion must be contiguous when flattened"
2342+ raise ValueError (f"{ requirement } ; axis { axis } is not selected in full." )
2343+ elif not _is_const_one (region .extent ):
2344+ spans_multiple_entries = True
2345+
2346+
23032347def _row_expand_binop_experiment (
23042348 dst ,
23052349 src0 ,
@@ -2319,31 +2363,54 @@ def _row_expand_binop_experiment(
23192363 dst_ptr , dst_shape = _handle_buffer_region_2d (dst , "w" )
23202364 else :
23212365 dst_ptr = dst .access_ptr ("w" )
2322- dst_shape = list (dst .shape [ - 2 :] )
2366+ dst_shape = _fold_nd_shape_to_2d (dst .shape )
23232367
23242368 if isinstance (src0 , BufferRegion ):
23252369 src0_ptr , src0_shape = _handle_buffer_region_2d (src0 , "r" )
23262370 else :
23272371 src0_ptr = src0 .access_ptr ("r" )
2328- src0_shape = list (src0 .shape [ - 2 :] )
2372+ src0_shape = _fold_nd_shape_to_2d (src0 .shape )
23292373
23302374 if isinstance (src1 , BufferRegion ):
2331- src1_ptr , src1_nd_extent = _handle_buffer_region (src1 , "r" )
2332- src1_full_shape = [src1_nd_extent [- 1 ]] if len (src1_nd_extent ) >= 2 else src1_nd_extent
2375+ src1_ptr , src1_full_shape = _handle_buffer_region_2d (src1 , "r" )
23332376 else :
23342377 src1_ptr = src1 .access_ptr ("r" )
2335- src1_full_shape = list (src1 .shape )
2378+ src1_full_shape = _fold_nd_shape_to_2d (src1 .shape )
23362379
2337- if len (src1_full_shape ) == 1 :
2380+ src0_buffer = src0 .buffer if isinstance (src0 , BufferRegion ) else src0
2381+ src1_buffer = src1 .buffer if isinstance (src1 , BufferRegion ) else src1
2382+ if DataType (src1_buffer .dtype ) != DataType (src0_buffer .dtype ):
2383+ mismatch = f"src1={ src1_buffer .dtype } , src0={ src0_buffer .dtype } "
2384+ raise ValueError (f"src1 and src0 dtypes must match: { mismatch } " )
2385+ if isinstance (src1 , BufferRegion ):
2386+ _validate_buffer_region_flat_contiguity (src1 )
2387+
2388+ dtype_bits = DataType (src0_buffer .dtype ).bits
2389+ block_bits = 32 * 8
2390+ if block_bits % dtype_bits != 0 :
2391+ raise ValueError (f"{ op_name } does not support dtype { src0_buffer .dtype } ." )
2392+ elems_per_block = block_bits // dtype_bits
2393+
2394+ if tmp is None :
2395+ if len (src1_full_shape ) != 2 :
2396+ requirement = f"packed src1 shape [R, { elems_per_block } ] when tmp is omitted"
2397+ raise ValueError (f"{ op_name } requires { requirement } ; got { src1_full_shape } ." )
2398+ s0 , s1 = src1_full_shape [- 2 ], src1_full_shape [- 1 ]
2399+ src1_len = s0
2400+ if not _const_equal (s1 , elems_per_block ):
2401+ requirement = f"packed src1 shape [R, { elems_per_block } ] when tmp is omitted"
2402+ raise ValueError (f"{ op_name } requires { requirement } ; got { src1_full_shape } ." )
2403+ elif len (src1_full_shape ) == 1 :
23382404 src1_len = src1_full_shape [0 ]
23392405 elif len (src1_full_shape ) == 2 :
23402406 s0 , s1 = src1_full_shape [- 2 ], src1_full_shape [- 1 ]
23412407 if _is_const_one (s0 ):
23422408 src1_len = s1
2343- elif _is_const_one (s1 ) or _const_equal ( s0 , dst_shape [ 0 ]) :
2409+ elif _is_const_one (s1 ):
23442410 src1_len = s0
23452411 else :
2346- raise ValueError (f"src1 must be 1D [R], [1, R], or [R, 1]; got { src1_full_shape } " )
2412+ requirement = "scalar src1 shape [R], [1, R], or [R, 1] when tmp is provided"
2413+ raise ValueError (f"{ op_name } requires { requirement } ; got { src1_full_shape } ." )
23472414 else :
23482415 raise ValueError (f"src1 must be 1D or 2D, got shape { src1_full_shape } " )
23492416
@@ -2354,9 +2421,33 @@ def _row_expand_binop_experiment(
23542421 raise ValueError (f"dst and src0 shapes must match: dst={ dst_shape } , src0={ src0_shape } " )
23552422
23562423 if not _const_equal (src1_len , dst_shape [0 ]):
2357- raise ValueError (f"src1 scalar count must match dst rows: src1={ src1_len } , dst[0]={ dst_shape [0 ]} " )
2424+ mismatch = f"src1={ src1_len } , dst[0]={ dst_shape [0 ]} "
2425+ raise ValueError (f"src1 scalar count must match dst rows: { mismatch } " )
2426+
2427+ if tmp is not None :
2428+ tmp_buffer = tmp .buffer if isinstance (tmp , BufferRegion ) else tmp
2429+ if DataType (tmp_buffer .dtype ) != DataType (src0_buffer .dtype ):
2430+ mismatch = f"tmp={ tmp_buffer .dtype } , src0={ src0_buffer .dtype } "
2431+ raise ValueError (f"tmp and src0 dtypes must match: { mismatch } " )
2432+ if isinstance (tmp , BufferRegion ):
2433+ _validate_buffer_region_flat_contiguity (tmp )
2434+ tmp_shape = [region .extent for region in tmp .region ]
2435+ else :
2436+ tmp_shape = list (tmp .shape )
2437+ tmp_size = math .prod (tmp_shape )
2438+ expected_tmp_size = dst_shape [0 ] * elems_per_block
2439+ if not _const_equal (tmp_size , expected_tmp_size ):
2440+ requirement = f"tmp must contain { expected_tmp_size } elements"
2441+ raise ValueError (f"{ op_name } { requirement } ; got { tmp_size } ." )
23582442
23592443 dtype = _dtype (src0 )
2444+ row_bits = 256 * 8
2445+ expected_cols = row_bits // dtype_bits
2446+ if not _const_equal (dst_shape [1 ], expected_cols ):
2447+ dtype_name = src0_buffer .dtype
2448+ requirement = f"a 256-byte last dimension ({ expected_cols } { dtype_name } elements)"
2449+ raise ValueError (f"{ op_name } requires { requirement } , got { dst_shape [1 ]} ." )
2450+
23602451 args = [
23612452 f"{ op_name } <{ dtype } >" ,
23622453 dst_ptr ,
@@ -2387,6 +2478,10 @@ def row_expand_mul_experiment(
23872478
23882479 AscendC: brcb(src1→tmp) + mul_mask(dst, src0, tmp).
23892480 PTO: TROWEXPANDMUL_row_vec(dst, src0, src1).
2481+
2482+ Contiguous leading dimensions of dst/src0 are folded into rows; each row
2483+ must be 256 bytes. Without tmp, src1 is packed as one 32-byte block per
2484+ row. With tmp, src1 is scalar-linear and tmp supplies those packed blocks.
23902485 """
23912486 return _row_expand_binop_experiment (
23922487 dst ,
@@ -2409,6 +2504,10 @@ def row_expand_sub_experiment(
24092504
24102505 AscendC: brcb(src1→tmp) + sub_mask(dst, src0, tmp).
24112506 PTO: TROWEXPANDSUB_row_vec(dst, src0, src1).
2507+
2508+ Contiguous leading dimensions of dst/src0 are folded into rows; each row
2509+ must be 256 bytes. Without tmp, src1 is packed as one 32-byte block per
2510+ row. With tmp, src1 is scalar-linear and tmp supplies those packed blocks.
24122511 """
24132512 return _row_expand_binop_experiment (
24142513 dst ,
@@ -2431,6 +2530,10 @@ def row_expand_div_experiment(
24312530
24322531 AscendC: brcb(src1→tmp) + div_mask(dst, src0, tmp).
24332532 PTO: TROWEXPANDDIV_row_vec(dst, src0, src1).
2533+
2534+ Contiguous leading dimensions of dst/src0 are folded into rows; each row
2535+ must be 256 bytes. Without tmp, src1 is packed as one 32-byte block per
2536+ row. With tmp, src1 is scalar-linear and tmp supplies those packed blocks.
24342537 """
24352538 return _row_expand_binop_experiment (
24362539 dst ,
0 commit comments