Skip to content

Commit ac930b9

Browse files
committed
fix(emit): correct whole_values emission for strided struct members
Signed-off-by: Francisco Freitas <francisco.freitas@ledger.fr>
1 parent 738c809 commit ac930b9

3 files changed

Lines changed: 319 additions & 80 deletions

File tree

src/cgen/emit.zig

Lines changed: 252 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -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 + ..."
94120
fn 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+
10271208
test "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+
11641376
test "emitChecker generates .values validation" {
11651377
const alloc = std.testing.allocator;
11661378
var tmp = std.testing.tmpDir(.{});

0 commit comments

Comments
 (0)