From fc263fc4b1ee36a3c4bfd7d730cccb57ebe1064a Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 26 Aug 2026 02:04:47 -0300 Subject: Raise the board's grid to 56x14, and make it a build option The 40x12 ceiling was never about the screen. It was about memory, and the comment above `max_cols` said so: "every cell is paid for four times over: vaxis keeps a Screen and an InternalScreen, pardes keeps its own Surface and previous_cells". Two of those four are now dead weight - with `direct_emit` the emitter diffs the Surface against its own shadow and writes the escapes itself, so vaxis's two grids are allocated, never read, and were the largest single claim on a 384 KiB heap. `init` sizes them to ONE CELL. vaxis still does the work only it can do: the alternate screen, the capability queries, and parsing everything that comes back. That removes the memory ceiling entirely - the heap now reports 336 KB free at every geometry tried, including ones that used to fail - and leaves latency as the only limit, which is the honest one: every frame walks the whole grid. ## Measured on the die, 0.87 us per cell geometry cells round trip 40x12 480 3,628 us the old default 56x14 784 3,930 us the new one 56x16 896 3,965 us 60x18 1,080 4,114 us 64x20 1,280 4,281 us 80x24 1,920 4,809 us 100x30 3,000 5,743 us 120x36 4,320 6,923 us 140x42 5,880 8,310 us the largest that runs 160x48 7,680 links, then traps 200x60 12,000 does not link 56x14 is 63% more area and 40% more width than 40x12 and still holds the 4 ms this port was built to. 56x16 was tried first: 3,965 us on the bench instrument but 4,029 on the phase-randomised one, which is over, and the two instruments differ by about 50 us systematically - so the wider grid went and two rows stayed behind. Width is worth more than height for reading code. The two failures at the top are worth naming precisely because they are different failures. 200x60 does not link: `.bss will not fit in region l2mem, overflowed by 76036 bytes`, that `.bss` being the shell's shadow copy of the grid, sized at comptime. 160x48 links and then TRAPS at boot - the same region pressure arriving at runtime as a collision rather than as a diagnostic. Neither is a heap problem any more, which is the interesting part: the heap has 336 KB spare while `.bss` runs out. `-Dp4-cols` / `-Dp4-rows` because none of the above is a constant. 80x24 is one flag away for anyone who would rather have the classic terminal than the millisecond. Verified at the new geometry rather than assumed: the A/B against the reference path - vaxis rendering, full repaint, `shadow_grid` and `direct_emit` both off - is identical in every cell, characters and resolved style. That matters more here than usual because the emitter's column arithmetic has a special case at the last column, and 40 was the only width it had ever been asked about. snap 95/95, hxdiff 481/0, hxparity 561/0, unit-test, both A/B arms, tty/p4/gui, and the board's own `p4-bench --check`. --- build.zig | 62 +++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 23 deletions(-) (limited to 'build.zig') diff --git a/build.zig b/build.zig index c9ae800f..ed3a2e6a 100644 --- a/build.zig +++ b/build.zig @@ -70,30 +70,32 @@ pub fn build(b: *std.Build) void { .cpu_model = .{ .explicit = &std.Target.riscv.cpu.generic_rv32 }, .cpu_features_add = riscvFeatures(&.{ .m, .a, .f, .c, .zicsr, .zifencei }), }; - const requested_target = b.standardTargetOptions(.{ .default_target = switch (platform) { - .macos => if (builtin.os.tag.isDarwin()) .{ - .cpu_arch = builtin.target.cpu.arch, - .os_tag = .macos, - .os_version_min = .{ .semver = macos_min_version }, - } else .{}, - // The P4 firmware target, spelled out here so `-Dplatform=p4` alone is a - // working command line. The CPU FEATURES are part of that spelling and - // are not optional: the object this build emits is linked into an image - // whose other halves are compiled `generic_rv32+m+a+f+c+zicsr+zifencei`, - // and `f` decides the float ABI. Leaving the model implicit produced a - // soft-float object and `ld.lld: cannot link object files with different - // floating-point ABI` — at LINK time in the other repo, far from here. - // Espressif's GCC adds the vendor extensions xesploop/xespv2p1 on top; - // upstream LLVM has neither and ordinary code never emits them, so this - // matches the base ISA the firmware uses exactly. - .p4 => p4_target, - .tty, .gui, .web => .{ - .cpu_arch = .x86_64, - .os_tag = .linux, - .abi = .gnu, - .glibc_version = .{ .major = 2, .minor = 38, .patch = 0 }, + const requested_target = b.standardTargetOptions(.{ + .default_target = switch (platform) { + .macos => if (builtin.os.tag.isDarwin()) .{ + .cpu_arch = builtin.target.cpu.arch, + .os_tag = .macos, + .os_version_min = .{ .semver = macos_min_version }, + } else .{}, + // The P4 firmware target, spelled out here so `-Dplatform=p4` alone is a + // working command line. The CPU FEATURES are part of that spelling and + // are not optional: the object this build emits is linked into an image + // whose other halves are compiled `generic_rv32+m+a+f+c+zicsr+zifencei`, + // and `f` decides the float ABI. Leaving the model implicit produced a + // soft-float object and `ld.lld: cannot link object files with different + // floating-point ABI` — at LINK time in the other repo, far from here. + // Espressif's GCC adds the vendor extensions xesploop/xespv2p1 on top; + // upstream LLVM has neither and ordinary code never emits them, so this + // matches the base ISA the firmware uses exactly. + .p4 => p4_target, + .tty, .gui, .web => .{ + .cpu_arch = .x86_64, + .os_tag = .linux, + .abi = .gnu, + .glibc_version = .{ .major = 2, .minor = 38, .patch = 0 }, + }, }, - } }); + }); // `standardTargetOptions` honours `default_target` ONLY when `-Dtarget` is absent, so the // documented `-Dplatform=p4 -Dtarget=riscv32-freestanding` discarded the CPU features above and // silently produced a soft-float object. The features are not a preference here - `f` decides @@ -346,6 +348,20 @@ pub fn build(b: *std.Build) void { opts.addOption(bool, "enable_tracy", tracy != null); opts.addOption(bool, "zls_backend", zls_backend); opts.addOption(bool, "mupdf", enable_mupdf); + // THE BOARD'S GRID, a build option because the right size is a measurement rather than a + // constant, and because two independent things limit it. + // + // LATENCY binds first, and linearly: every frame walks the whole grid, at 0.87 us per cell + // measured on the die. 56x14 is 784 cells and a 3,930 us round trip - 63% more area and 40% more + // width than the 40x12 it replaces, and still inside the 4 ms this port was built to hold. 56x16 + // was tried first and measured 4,029, which is over; 80x24 works and costs 4,809. The full curve + // is in `05-zig-p4/experiments/report.typ`. + // + // MEMORY binds much later, and only since vaxis's two unused grids stopped being allocated: + // 140x42 runs, 160x48 links and then traps, and 200x60 does not link at all - `.bss will not fit + // in region l2mem, overflowed by 76036 bytes`, that being the shell's shadow copy of the grid. + opts.addOption(u16, "p4_cols", b.option(u16, "p4-cols", "board grid width in cells (p4 only)") orelse 56); + opts.addOption(u16, "p4_rows", b.option(u16, "p4-rows", "board grid height in cells (p4 only)") orelse 14); // Meaningful only for the SDL shell. Keeping the platform condition here // prevents Config/EffectCode from describing tty/macOS/web as "prebuilt". opts.addOption(bool, "gui_shader_sources_prebuilt", gui_shader_sources_prebuilt); -- cgit v1.3