@@ -90,6 +90,32 @@ fn mangleName(allocator: std.mem.Allocator, path: []const u8, symbol: []const u8
9090 return std .fmt .allocPrint (allocator , "{s}_{s}" , .{ sanitized , symbol });
9191}
9292
93+ /// Build a C expression that indexes into a dense whole_values blob using the
94+ /// loop variables from the field dimensions. The blob stores elements
95+ /// sequentially (no stride gaps), so the offset for element (i_k, i_k+1, ...)
96+ /// is: src_base + (i_k * inner_product_k+1 + i_k+1 * inner_product_k+2 + ...) * elem_bytes
97+ fn emitBlobOffsetExpr (
98+ buf : []u8 ,
99+ src_base : []const u8 ,
100+ field_dims : []const ir.Dimension ,
101+ global_dims_len : usize ,
102+ elem_bytes : usize ,
103+ ) ! []const u8 {
104+ var stream = std .io .fixedBufferStream (buf );
105+ const w = stream .writer ();
106+ try w .writeAll (src_base );
107+
108+ var inner_product : usize = elem_bytes ;
109+ var i : usize = field_dims .len ;
110+ while (i > 0 ) {
111+ i -= 1 ;
112+ const idx = global_dims_len + i ;
113+ try w .print (" + i{d} * {d}" , .{ idx , inner_product });
114+ inner_product *= field_dims [i ].len ;
115+ }
116+ return stream .getWritten ();
117+ }
118+
93119/// Generate offset calculation string: "base + i0*s0 + i1*s1 + ..."
94120fn emitOffsetCalc (
95121 allocator : std.mem.Allocator ,
@@ -336,12 +362,6 @@ fn emitSampler(allocator: std.mem.Allocator, globals: []const Parser.Global, fil
336362 const vals = f .domain .whole_values ;
337363 if (vals .len == 0 ) continue ;
338364
339- const offset_expr = try emitOffsetCalc (allocator , g .dims , &.{}, @intCast (f .offset_bits / 8 ));
340- defer allocator .free (offset_expr );
341- const dst_expr = try std .fmt .allocPrint (allocator , "&{s}[{s}]" , .{ mangled , offset_expr });
342- defer allocator .free (dst_expr );
343- const blob_str = try std .fmt .bufPrint (& bytes_buf , "{d}" , .{ir .wholeFieldBytes (f )});
344-
345365 const current_depth = loop_stack .depth ();
346366 var label_buf : [64 ]u8 = undefined ;
347367 const label = try std .fmt .bufPrint (& label_buf , "FM_WVAL_{d}" , .{wval_idx });
@@ -355,14 +375,56 @@ fn emitSampler(allocator: std.mem.Allocator, globals: []const Parser.Global, fil
355375 const count_str = try std .fmt .bufPrint (& num_buf , "{d}" , .{vals .len });
356376 try file .writeAll (count_str );
357377 try file .writeAll (";\n " );
358- var src_buf : [256 ]u8 = undefined ;
359- const src = try std .fmt .bufPrint (& src_buf , "&{s}[idx_{s} * {s}_BLOB_BYTES]" , .{ label , label , label });
360- try emitMemcpy (file , current_depth , dst_expr , src , blob_str );
361- try incrementOffset (file , current_depth , "1" );
378+ }
379+
380+ if (ir .isWholeFieldDense (f )) {
381+ const offset_expr = try emitOffsetCalc (allocator , g .dims , &.{}, @intCast (f .offset_bits / 8 ));
382+ defer allocator .free (offset_expr );
383+ const dst_expr = try std .fmt .allocPrint (allocator , "&{s}[{s}]" , .{ mangled , offset_expr });
384+ defer allocator .free (dst_expr );
385+ const blob_str = try std .fmt .bufPrint (& bytes_buf , "{d}" , .{ir .wholeFieldBytes (f )});
386+
387+ if (vals .len > 1 ) {
388+ var src_buf : [256 ]u8 = undefined ;
389+ const src = try std .fmt .bufPrint (& src_buf , "&{s}[idx_{s} * {s}_BLOB_BYTES]" , .{ label , label , label });
390+ try emitMemcpy (file , current_depth , dst_expr , src , blob_str );
391+ } else {
392+ var src_buf : [256 ]u8 = undefined ;
393+ const src = try std .fmt .bufPrint (& src_buf , "&{s}[0]" , .{label });
394+ try emitMemcpy (file , current_depth , dst_expr , src , blob_str );
395+ }
362396 } else {
397+ const src_base = if (vals .len > 1 )
398+ try std .fmt .allocPrint (allocator , "idx_{s} * {s}_BLOB_BYTES" , .{ label , label })
399+ else
400+ try std .fmt .allocPrint (allocator , "0" , .{});
401+ defer allocator .free (src_base );
402+
403+ for (f .dims , 0.. ) | d , fi | {
404+ const i = global_dims_len + fi ;
405+ try loop_stack .openLoop (d , i );
406+ }
407+ const inner_depth = loop_stack .depth ();
408+
409+ const offset_expr = try emitOffsetCalc (allocator , g .dims , f .dims , @intCast (f .offset_bits / 8 ));
410+ defer allocator .free (offset_expr );
411+ const dst_expr = try std .fmt .allocPrint (allocator , "&{s}[{s}]" , .{ mangled , offset_expr });
412+ defer allocator .free (dst_expr );
413+
414+ var blob_off_buf : [256 ]u8 = undefined ;
415+ const elem_bytes = ir .elementBytes (f );
416+ const blob_off_expr = try emitBlobOffsetExpr (& blob_off_buf , src_base , f .dims , global_dims_len , elem_bytes );
363417 var src_buf : [256 ]u8 = undefined ;
364- const src = try std .fmt .bufPrint (& src_buf , "&{s}[0]" , .{label });
365- try emitMemcpy (file , current_depth , dst_expr , src , blob_str );
418+ const src = try std .fmt .bufPrint (& src_buf , "&{s}[{s}]" , .{ label , blob_off_expr });
419+
420+ const eb_str = try std .fmt .bufPrint (& bytes_buf , "{d}" , .{elem_bytes });
421+ try emitMemcpy (file , inner_depth , dst_expr , src , eb_str );
422+
423+ try loop_stack .closeLoops (field_dims_len );
424+ }
425+
426+ if (vals .len > 1 ) {
427+ try incrementOffset (file , current_depth , "1" );
366428 }
367429 continue ;
368430 }
@@ -520,40 +582,99 @@ fn emitChecker(allocator: std.mem.Allocator, globals: []const Parser.Global, fil
520582 const vals = f .domain .whole_values ;
521583 if (vals .len == 0 ) continue ;
522584
523- const offset_expr = try emitOffsetCalc (allocator , g .dims , &.{}, @intCast (f .offset_bits / 8 ));
524- defer allocator .free (offset_expr );
525585 const current_depth = loop_stack .depth ();
526586
527587 var label_buf : [64 ]u8 = undefined ;
528588 const label = try std .fmt .bufPrint (& label_buf , "FM_WVAL_{d}" , .{wval_idx });
529589 wval_idx += 1 ;
530590
531- try writeIndent (file , current_depth );
532- try file .writeAll ("{\n " );
533- try writeIndent (file , current_depth + 1 );
534- try file .writeAll ("int found = 0;\n " );
535- try writeIndent (file , current_depth + 1 );
536- try file .writeAll ("for (size_t vi = 0; vi < " );
537- try file .writeAll (label );
538- try file .writeAll ("_COUNT; vi++) {\n " );
539- try writeIndent (file , current_depth + 2 );
540- try file .writeAll ("if (memcmp(&" );
541- try file .writeAll (mangled );
542- try file .writeAll ("[" );
543- try file .writeAll (offset_expr );
544- try file .writeAll ("], &" );
545- try file .writeAll (label );
546- try file .writeAll ("[vi * " );
547- try file .writeAll (label );
548- try file .writeAll ("_BLOB_BYTES], " );
549- try file .writeAll (label );
550- try file .writeAll ("_BLOB_BYTES) == 0) { found = 1; break; }\n " );
551- try writeIndent (file , current_depth + 1 );
552- try file .writeAll ("}\n " );
553- try writeIndent (file , current_depth + 1 );
554- try file .writeAll ("if (!found) return -1;\n " );
555- try writeIndent (file , current_depth );
556- try file .writeAll ("}\n " );
591+ if (ir .isWholeFieldDense (f )) {
592+ const offset_expr = try emitOffsetCalc (allocator , g .dims , &.{}, @intCast (f .offset_bits / 8 ));
593+ defer allocator .free (offset_expr );
594+
595+ try writeIndent (file , current_depth );
596+ try file .writeAll ("{\n " );
597+ try writeIndent (file , current_depth + 1 );
598+ try file .writeAll ("int found = 0;\n " );
599+ try writeIndent (file , current_depth + 1 );
600+ try file .writeAll ("for (size_t vi = 0; vi < " );
601+ try file .writeAll (label );
602+ try file .writeAll ("_COUNT; vi++) {\n " );
603+ try writeIndent (file , current_depth + 2 );
604+ try file .writeAll ("if (memcmp(&" );
605+ try file .writeAll (mangled );
606+ try file .writeAll ("[" );
607+ try file .writeAll (offset_expr );
608+ try file .writeAll ("], &" );
609+ try file .writeAll (label );
610+ try file .writeAll ("[vi * " );
611+ try file .writeAll (label );
612+ try file .writeAll ("_BLOB_BYTES], " );
613+ try file .writeAll (label );
614+ try file .writeAll ("_BLOB_BYTES) == 0) { found = 1; break; }\n " );
615+ try writeIndent (file , current_depth + 1 );
616+ try file .writeAll ("}\n " );
617+ try writeIndent (file , current_depth + 1 );
618+ try file .writeAll ("if (!found) return -1;\n " );
619+ try writeIndent (file , current_depth );
620+ try file .writeAll ("}\n " );
621+ } else {
622+ const blob_bytes = ir .wholeFieldBytes (f );
623+ const blob_str = try std .fmt .bufPrint (& bytes_buf , "{d}" , .{blob_bytes });
624+ const elem_bytes = ir .elementBytes (f );
625+
626+ try writeIndent (file , current_depth );
627+ try file .writeAll ("{\n " );
628+
629+ // Declare a local buffer and gather strided elements into it.
630+ try writeIndent (file , current_depth + 1 );
631+ try file .writeAll ("uint8_t wvbuf[" );
632+ try file .writeAll (blob_str );
633+ try file .writeAll ("];\n " );
634+
635+ for (f .dims , 0.. ) | d , fi | {
636+ const i = global_dims_len + fi ;
637+ try loop_stack .openLoop (d , i );
638+ }
639+ const gather_depth = loop_stack .depth ();
640+
641+ const offset_expr = try emitOffsetCalc (allocator , g .dims , f .dims , @intCast (f .offset_bits / 8 ));
642+ defer allocator .free (offset_expr );
643+ const src_expr = try std .fmt .allocPrint (allocator , "&{s}[{s}]" , .{ mangled , offset_expr });
644+ defer allocator .free (src_expr );
645+
646+ var blob_off_buf : [256 ]u8 = undefined ;
647+ const blob_off_expr = try emitBlobOffsetExpr (& blob_off_buf , "0" , f .dims , global_dims_len , elem_bytes );
648+ var dst_buf : [256 ]u8 = undefined ;
649+ const dst_expr = try std .fmt .bufPrint (& dst_buf , "&wvbuf[{s}]" , .{blob_off_expr });
650+
651+ var eb_buf : [64 ]u8 = undefined ;
652+ const eb_str_2 = try std .fmt .bufPrint (& eb_buf , "{d}" , .{elem_bytes });
653+ try emitMemcpy (file , gather_depth , dst_expr , src_expr , eb_str_2 );
654+
655+ try loop_stack .closeLoops (field_dims_len );
656+
657+ try writeIndent (file , current_depth + 1 );
658+ try file .writeAll ("int found = 0;\n " );
659+ try writeIndent (file , current_depth + 1 );
660+ try file .writeAll ("for (size_t vi = 0; vi < " );
661+ try file .writeAll (label );
662+ try file .writeAll ("_COUNT; vi++) {\n " );
663+ try writeIndent (file , current_depth + 2 );
664+ try file .writeAll ("if (memcmp(wvbuf, &" );
665+ try file .writeAll (label );
666+ try file .writeAll ("[vi * " );
667+ try file .writeAll (label );
668+ try file .writeAll ("_BLOB_BYTES], " );
669+ try file .writeAll (label );
670+ try file .writeAll ("_BLOB_BYTES) == 0) { found = 1; break; }\n " );
671+ try writeIndent (file , current_depth + 1 );
672+ try file .writeAll ("}\n " );
673+ try writeIndent (file , current_depth + 1 );
674+ try file .writeAll ("if (!found) return -1;\n " );
675+ try writeIndent (file , current_depth );
676+ try file .writeAll ("}\n " );
677+ }
557678 continue ;
558679 }
559680
@@ -1024,6 +1145,66 @@ test "emitSampler with .whole_values singleton uses no selector byte" {
10241145 try std .testing .expect (std .mem .indexOf (u8 , out , "off += 1" ) == null );
10251146}
10261147
1148+ test "emitSampler with strided .whole_values scatters elements" {
1149+ const alloc = std .testing .allocator ;
1150+ var tmp = std .testing .tmpDir (.{});
1151+ defer tmp .cleanup ();
1152+ var file = try createTmpFile (& tmp , "t.c" );
1153+ defer file .close ();
1154+ const fields : []Parser.Field = @constCast (&[_ ]Parser.Field {.{
1155+ .name = ".items.b" ,
1156+ .bit_width = 8 ,
1157+ .is_padding = false ,
1158+ .dims = &.{.{ .len = 4 , .stride_bytes = 4 }},
1159+ .domain = .{ .whole_values = &.{ &[_ ]u8 { 0x10 , 0x20 , 0x30 , 0x40 }, &[_ ]u8 { 0xa0 , 0xb0 , 0xc0 , 0xd0 } } },
1160+ }});
1161+ const globals : []const Parser.Global = &.{.{
1162+ .name = "pkt" ,
1163+ .source_file = "" ,
1164+ .size_bytes = 16 ,
1165+ .is_static = false ,
1166+ .dims = &.{},
1167+ .fields = fields ,
1168+ }};
1169+ try emitSampler (alloc , globals , & file );
1170+ var buf : [8192 ]u8 = undefined ;
1171+ const out = try readTmpFile (& tmp , "t.c" , & buf );
1172+ try std .testing .expect (std .mem .indexOf (u8 , out , "idx_FM_WVAL_0 = data[off] % 2" ) != null );
1173+ try std .testing .expect (std .mem .indexOf (u8 , out , "for (size_t i0 = 0; i0 < 4; i0++)" ) != null );
1174+ try std .testing .expect (std .mem .indexOf (u8 , out , "memcpy(&pkt[0 + i0 * 4], &FM_WVAL_0[idx_FM_WVAL_0 * FM_WVAL_0_BLOB_BYTES + i0 * 1], 1)" ) != null );
1175+ try std .testing .expect (std .mem .indexOf (u8 , out , "off += 1" ) != null );
1176+ }
1177+
1178+ test "emitSampler with strided .whole_values singleton scatters without selector" {
1179+ const alloc = std .testing .allocator ;
1180+ var tmp = std .testing .tmpDir (.{});
1181+ defer tmp .cleanup ();
1182+ var file = try createTmpFile (& tmp , "t.c" );
1183+ defer file .close ();
1184+ const fields : []Parser.Field = @constCast (&[_ ]Parser.Field {.{
1185+ .name = ".items.b" ,
1186+ .bit_width = 8 ,
1187+ .is_padding = false ,
1188+ .dims = &.{.{ .len = 4 , .stride_bytes = 4 }},
1189+ .domain = .{ .whole_values = &.{&[_ ]u8 { 0x10 , 0x20 , 0x30 , 0x40 }} },
1190+ }});
1191+ const globals : []const Parser.Global = &.{.{
1192+ .name = "pkt" ,
1193+ .source_file = "" ,
1194+ .size_bytes = 16 ,
1195+ .is_static = false ,
1196+ .dims = &.{},
1197+ .fields = fields ,
1198+ }};
1199+ try emitSampler (alloc , globals , & file );
1200+ var buf : [8192 ]u8 = undefined ;
1201+ const out = try readTmpFile (& tmp , "t.c" , & buf );
1202+ try std .testing .expect (std .mem .indexOf (u8 , out , "for (size_t i0 = 0; i0 < 4; i0++)" ) != null );
1203+ try std .testing .expect (std .mem .indexOf (u8 , out , "memcpy(&pkt[0 + i0 * 4], &FM_WVAL_0[0 + i0 * 1], 1)" ) != null );
1204+ try std .testing .expect (std .mem .indexOf (u8 , out , "idx_FM_WVAL_0" ) == null );
1205+ try std .testing .expect (std .mem .indexOf (u8 , out , "off += 1" ) == null );
1206+ }
1207+
10271208test "emitSampler with .pointers domain" {
10281209 const alloc = std .testing .allocator ;
10291210 var tmp = std .testing .tmpDir (.{});
@@ -1161,6 +1342,37 @@ test "emitChecker generates .whole_values validation" {
11611342 try std .testing .expect (std .mem .indexOf (u8 , out , "memcmp(&pkt[0], &FM_WVAL_0[vi * FM_WVAL_0_BLOB_BYTES], FM_WVAL_0_BLOB_BYTES)" ) != null );
11621343}
11631344
1345+ test "emitChecker generates strided .whole_values gather-then-compare" {
1346+ const alloc = std .testing .allocator ;
1347+ var tmp = std .testing .tmpDir (.{});
1348+ defer tmp .cleanup ();
1349+ var file = try createTmpFile (& tmp , "t.c" );
1350+ defer file .close ();
1351+ const fields : []Parser.Field = @constCast (&[_ ]Parser.Field {.{
1352+ .name = ".items.b" ,
1353+ .bit_width = 8 ,
1354+ .is_padding = false ,
1355+ .dims = &.{.{ .len = 4 , .stride_bytes = 4 }},
1356+ .domain = .{ .whole_values = &.{ &[_ ]u8 { 0x10 , 0x20 , 0x30 , 0x40 }, &[_ ]u8 { 0xa0 , 0xb0 , 0xc0 , 0xd0 } } },
1357+ }});
1358+ const globals : []const Parser.Global = &.{.{
1359+ .name = "pkt" ,
1360+ .source_file = "" ,
1361+ .size_bytes = 16 ,
1362+ .is_static = false ,
1363+ .dims = &.{},
1364+ .fields = fields ,
1365+ }};
1366+ try emitChecker (alloc , globals , & file );
1367+ var buf : [8192 ]u8 = undefined ;
1368+ const out = try readTmpFile (& tmp , "t.c" , & buf );
1369+ try std .testing .expect (std .mem .indexOf (u8 , out , "uint8_t wvbuf[4]" ) != null );
1370+ try std .testing .expect (std .mem .indexOf (u8 , out , "for (size_t i0 = 0; i0 < 4; i0++)" ) != null );
1371+ try std .testing .expect (std .mem .indexOf (u8 , out , "memcpy(&wvbuf[0 + i0 * 1], &pkt[0 + i0 * 4], 1)" ) != null );
1372+ try std .testing .expect (std .mem .indexOf (u8 , out , "memcmp(wvbuf, &FM_WVAL_0[vi * FM_WVAL_0_BLOB_BYTES], FM_WVAL_0_BLOB_BYTES)" ) != null );
1373+ try std .testing .expect (std .mem .indexOf (u8 , out , "if (!found) return -1" ) != null );
1374+ }
1375+
11641376test "emitChecker generates .values validation" {
11651377 const alloc = std .testing .allocator ;
11661378 var tmp = std .testing .tmpDir (.{});
0 commit comments