@@ -1149,10 +1149,17 @@ def handle_write(emitter: WaveEmitter, node: fx.Node):
11491149 is_shared = get_custom (memory ).type .address_space == SHARED_ADDRESS_SPACE
11501150 is_bf16 = isinstance (element_type , BF16Type )
11511151
1152- # TODO: Replace _shuffle_xor_pack with a formal Write node attribute.
1153- if is_shared and is_bf16 and getattr (node , "_shuffle_xor_pack" , False ):
1154- _write_shuffle_xor16_b128_to_lds (
1155- emitter , insert_vector , kb_dest , start_indices_th
1152+ if not is_shared and is_bf16 and getattr (node , "_permlane_pack_global" , False ):
1153+ _write_permlane_pack_to_global (
1154+ emitter ,
1155+ insert_vector ,
1156+ kb_dest ,
1157+ output_shape ,
1158+ start_indices ,
1159+ start_indices_wg ,
1160+ start_indices_th ,
1161+ get_custom (memory ),
1162+ index ,
11561163 )
11571164 return
11581165
@@ -1177,68 +1184,113 @@ def handle_write(emitter: WaveEmitter, node: fx.Node):
11771184 )
11781185
11791186
1180- def _write_shuffle_xor16_b128_to_lds (
1187+ def _write_permlane_pack_to_global (
11811188 emitter : WaveEmitter ,
11821189 insert_vector : Value ,
1183- mem : Value ,
1190+ kb_dest : Value ,
1191+ output_shape : tuple ,
1192+ start_indices : tuple ,
1193+ start_indices_wg : tuple ,
11841194 start_indices_th : tuple ,
1195+ memory_custom ,
1196+ index : dict ,
11851197):
1186- """Combine own 4 bf16 with partner lane's 4 bf16 via shuffle XOR 16,
1187- producing a vector<4xi32> (8 packed bf16) written as ds_write_b128.
1198+ """Pack two lanes' bf16 values via permlane16_swap for wide global stores.
11881199
11891200 MMA accumulator layout (F32_16x16x128_F8F6F4) gives each thread 4
1190- consecutive M values. Lanes are grouped by 16: lanes 0-15 own M=0-3,
1191- lanes 16-31 own M=4-7, etc. Shuffle XOR 16 exchanges between paired
1192- groups, giving each lane 8 consecutive M values.
1201+ consecutive M values. Lanes are grouped by 16: lanes 0-15 own M=0-3,
1202+ lanes 16-31 own M=4-7, etc. ``v_permlane16_swap_b32`` exchanges data
1203+ between paired groups, giving each lane 8 consecutive M values that
1204+ can be written as a single ``buffer_store_dwordx4`` (128 bits).
1205+
1206+ Both lane halves produce identical data at the same address (benign
1207+ duplicate store):
1208+
1209+ - Lower half (lanes 0-15 in each 32-lane group):
1210+ data = [own, partner], address = thread's original M index.
1211+ - Upper half (lanes 16-31):
1212+ data = [partner, own], address = original M index - 4.
11931213
1194- Both lane halves write: the lower half uses [own, partner] ordering,
1195- the upper half uses [partner, own] with an adjusted address, so both
1196- produce identical data at the same LDS location.
1214+ This dual-write avoids divergent control flow (no scf.if / exec
1215+ masking needed). The buffer descriptor's ``valid_bytes`` handles
1216+ out-of-bounds suppression for dynamic shapes.
1217+
1218+ Precondition: M must be the innermost (last) memory dimension with
1219+ stride 1 (i.e. transpose_output=True, shape [N, M]).
11971220 """
11981221 bf16_type = BF16Type .get ()
11991222 i32_type = IntegerType .get_signless (32 )
1223+ idx_type = IndexType .get ()
12001224 v2i32_type = VectorType .get ([2 ], i32_type )
12011225 v4i32_type = VectorType .get ([4 ], i32_type )
12021226 v8bf16_type = VectorType .get ([8 ], bf16_type )
1203- idx_type = IndexType .get ()
12041227
1228+ # Bitcast 4 x bf16 -> 2 x i32 so permlane16_swap can operate on dwords.
12051229 i32_vec = vector_d .bitcast (v2i32_type , insert_vector )
1206- own_0 = vector_d .extract (i32_vec , static_position = [0 ], dynamic_position = [])
1207- own_1 = vector_d .extract (i32_vec , static_position = [1 ], dynamic_position = [])
1230+ own_lo = vector_d .extract (i32_vec , static_position = [0 ], dynamic_position = [])
1231+ own_hi = vector_d .extract (i32_vec , static_position = [1 ], dynamic_position = [])
12081232
1209- swap_result_type = llvm_d .StructType .get_literal ([i32_type , i32_type ])
1210- swap_0 = rocdl_d .permlane16_swap (swap_result_type , own_0 , own_0 , False , False )
1211- partner_0 = llvm_d .extractvalue (i32_type , swap_0 , [0 ])
1212- swap_1 = rocdl_d .permlane16_swap (swap_result_type , own_1 , own_1 , False , False )
1213- partner_1 = llvm_d .extractvalue (i32_type , swap_1 , [0 ])
1233+ # Exchange dwords with the partner lane 16 positions apart.
1234+ swap_type = llvm_d .StructType .get_literal ([i32_type , i32_type ])
1235+ partner_lo = llvm_d .extractvalue (
1236+ i32_type , rocdl_d .permlane16_swap (swap_type , own_lo , own_lo , False , False ), [0 ]
1237+ )
1238+ partner_hi = llvm_d .extractvalue (
1239+ i32_type , rocdl_d .permlane16_swap (swap_type , own_hi , own_hi , False , False ), [0 ]
1240+ )
12141241
1215- lane_id = emitter .thread_ids [0 ]
1216- lane_in_wave = arith_d .remui (lane_id , arith_d .constant (idx_type , 64 ))
1242+ # Classify this lane as lower (0-15) or upper (16-31) within each
1243+ # 32-lane half-wave.
1244+ lane_in_wave = arith_d .remui (emitter .thread_ids [0 ], arith_d .constant (idx_type , 64 ))
12171245 half_pos = arith_d .remui (lane_in_wave , arith_d .constant (idx_type , 32 ))
12181246 is_lower = arith_d .cmpi (
1219- arith_d .CmpIPredicate .ult ,
1220- half_pos ,
1221- arith_d .constant (idx_type , 16 ),
1247+ arith_d .CmpIPredicate .ult , half_pos , arith_d .constant (idx_type , 16 )
12221248 )
12231249
1224- v0 = arith_d .select (is_lower , own_0 , partner_0 )
1225- v1 = arith_d .select (is_lower , own_1 , partner_1 )
1226- v2 = arith_d .select (is_lower , partner_0 , own_0 )
1227- v3 = arith_d .select (is_lower , partner_1 , own_1 )
1250+ # Both halves build identical 8-bf16 vectors, but in complementary
1251+ # order so they land at the same memory address:
1252+ # lower: [own_lo, own_hi, partner_lo, partner_hi] @ M
1253+ # upper: [partner_lo, partner_hi, own_lo, own_hi] @ M - 4
1254+ d0 = arith_d .select (is_lower , own_lo , partner_lo )
1255+ d1 = arith_d .select (is_lower , own_hi , partner_hi )
1256+ d2 = arith_d .select (is_lower , partner_lo , own_lo )
1257+ d3 = arith_d .select (is_lower , partner_hi , own_hi )
12281258
1229- wide_i32 = vector_d .from_elements (v4i32_type , [v0 , v1 , v2 , v3 ])
1259+ wide_i32 = vector_d .from_elements (v4i32_type , [d0 , d1 , d2 , d3 ])
12301260 wide_vec = vector_d .bitcast (v8bf16_type , wide_i32 )
12311261
1232- lds_offset = start_indices_th [0 ]
1233- four_elems = arith_d .constant (idx_type , 4 )
1234- adjusted_offset = arith_d .subi (lds_offset , four_elems )
1235- final_offset = arith_d .select (is_lower , lds_offset , adjusted_offset )
1262+ # Adjust the M (last) dimension index for the upper half so both
1263+ # halves target the same 8-element span starting at the lower half's
1264+ # M base. M is contiguous (stride 1), so subtracting 4 elements
1265+ # from the index subtracts 4 from the linearized element offset.
1266+ four = arith_d .constant (idx_type , 4 )
1267+
1268+ adj_th = list (start_indices_th )
1269+ adj_th [- 1 ] = arith_d .select (is_lower , adj_th [- 1 ], arith_d .subi (adj_th [- 1 ], four ))
12361270
1237- # Both lane halves (lower and upper within each 32-lane group) write
1238- # identical data to the same LDS address, so half the ds_write_b128
1239- # instructions are redundant.
1240- # TODO: Mask out the upper half to cut LDS write traffic in half.
1241- vector_d .store (wide_vec , mem , [final_offset ])
1271+ adj_full = list (start_indices )
1272+ adj_full [- 1 ] = arith_d .select (
1273+ is_lower , adj_full [- 1 ], arith_d .subi (adj_full [- 1 ], four )
1274+ )
1275+
1276+ # mask=None: the buffer descriptor encodes valid_bytes for the full
1277+ # output buffer, so OOB stores at tile edges are silently dropped by
1278+ # hardware. The original Write node's mask was sized for 4 elements,
1279+ # incompatible with our 8-element vector.
1280+ _create_vec_read_write (
1281+ emitter ,
1282+ output_shape ,
1283+ kb_dest ,
1284+ wide_vec ,
1285+ None ,
1286+ tuple (adj_full ),
1287+ start_indices_wg ,
1288+ tuple (adj_th ),
1289+ 8 ,
1290+ memory_custom ,
1291+ None ,
1292+ node_index = index ,
1293+ )
12421294
12431295
12441296def assume_index_subgroup_uniform (value : Value , element_type : IrType ) -> Value :
0 commit comments