tsb: third size pass — restructure to the oracle's shape
The second pass concluded the 168 B tricks->asm gap was per-call ABI
cost. Most of it was structure. Rebuilt around the oracle's own shape —
argless noinline primitives over a whole-loader call-saved register
protocol (g_addr in Y, count r16, window r7, direction latch r6), a
top-down erase_below whose loop tests against zero and hands callers
g_addr = 0 for free, bounded rx everywhere (a silent host unwinds to
the app from any state, as the oracle does), and a named tsb_app entry
that --pmem-wrap-around=32k relaxes to the wrapped rjmp:
tsb_asm 510 B in the 512 B section (oracle: 500), C++ except rx
and the page-store loop — the two routines whose remaining
cost is the calling convention itself (~30 asm lines, was
~280)
tsb_tricks 526 B, no assembly at all (was 666)
tsb_pure 836 B, still one readable function per command (was 842)
Every g_* update placement works around a GCC 16.1 wrong-code bug
(stores into global register variables deleted when only callees read
them — repro and rules in libavr dev/lessons.md). Also fixes two
latent hardware bugs all earlier tiers carried, masked by simavr's
zeroed register file: the crt-less entries never established
__zero_reg__ = 0, and the direction latch was read before written —
power-on registers are undefined.
All tiers full oracle feature parity, protocol tests green in both
libavr modes, .text byte-identical across modes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -45,7 +45,7 @@ constexpr std::uint16_t app_end = spm::flash_bytes - boot_bytes - page;
|
||||
constexpr std::uint16_t eeprom_end = avr::hw::db.mem.eeprom_size - 1;
|
||||
|
||||
// Firmware version stamp: YY*512 + MM*32 + DD, the encoding the host decodes.
|
||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 19;
|
||||
constexpr std::uint16_t build_date = 26 * 512 + 7 * 32 + 20;
|
||||
|
||||
// The 16-byte device-info block the host reads on activation. A flash_table
|
||||
// keeps it in progmem with no .data image (there is no crt to copy one).
|
||||
@@ -129,21 +129,27 @@ void erase_page(std::uint16_t addr)
|
||||
spm::wait();
|
||||
}
|
||||
|
||||
// Erase the whole application, one page at a time (unwritten pages stay erased).
|
||||
// Erase the whole application, one page at a time, top-down as the reference
|
||||
// loader does (unwritten pages stay erased and the host cannot observe the
|
||||
// order; the loop bound becomes a compare with zero).
|
||||
void erase_application()
|
||||
{
|
||||
for (std::uint16_t a = 0; a < app_end; a += page)
|
||||
for (std::uint16_t a = app_end; a != 0;) {
|
||||
a -= page;
|
||||
erase_page(a);
|
||||
}
|
||||
spm::rww_enable<off>();
|
||||
}
|
||||
|
||||
// Run the application: reset vector at 0x0000. Any non-command byte, a wrong
|
||||
// password, or an idle programmer port lands here.
|
||||
// The application's reset vector; the linker pins it to 0x0000 (--defsym).
|
||||
extern "C" [[noreturn]] void tsb_app();
|
||||
|
||||
// Run the application. Any non-command byte, a wrong password, or an idle
|
||||
// programmer port lands here.
|
||||
[[noreturn]] void appjump()
|
||||
{
|
||||
spm::wait(); // make sure any pending SPM finished before handing over
|
||||
reinterpret_cast<void (*)()>(0)();
|
||||
__builtin_unreachable();
|
||||
tsb_app();
|
||||
}
|
||||
|
||||
// 'f': stream the application flash back, one page per host '!'. Self-terminates
|
||||
@@ -295,5 +301,8 @@ gate password_gate()
|
||||
extern "C" [[gnu::naked, gnu::used, gnu::section(".vectors")]] void __boot_entry()
|
||||
{
|
||||
SP = RAMEND;
|
||||
// The one line of crt this loader needs: compiled code assumes
|
||||
// __zero_reg__ (r1) is 0, and power-on registers are undefined.
|
||||
asm volatile("clr __zero_reg__");
|
||||
tsb::run();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user