From 70ffadab6cd4d6f4ec2a31cbe6c42ba50dbaf05b Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Mon, 9 Mar 2026 07:11:03 +0000 Subject: [PATCH] PR005: sync ABI verifier/loader and regenerate stress cartridge --- .../prometeu-hal/src/syscalls/tests.rs | 100 ++++++++++++ .../prometeu-vm/src/virtual_machine.rs | 144 ++++++++++++++++++ crates/tools/pbxgen-stress/src/lib.rs | 16 +- test-cartridges/stress-console/program.pbx | Bin 825 -> 933 bytes 4 files changed, 258 insertions(+), 2 deletions(-) diff --git a/crates/console/prometeu-hal/src/syscalls/tests.rs b/crates/console/prometeu-hal/src/syscalls/tests.rs index ade9cda7..b71466db 100644 --- a/crates/console/prometeu-hal/src/syscalls/tests.rs +++ b/crates/console/prometeu-hal/src/syscalls/tests.rs @@ -167,3 +167,103 @@ fn declared_resolver_rejects_abi_mismatch() { } ); } + +#[test] +fn status_first_syscall_signatures_are_pinned() { + let set_sprite = meta_for(Syscall::GfxSetSprite); + assert_eq!(set_sprite.arg_slots, 10); + assert_eq!(set_sprite.ret_slots, 1); + + let audio_play_sample = meta_for(Syscall::AudioPlaySample); + assert_eq!(audio_play_sample.arg_slots, 5); + assert_eq!(audio_play_sample.ret_slots, 1); + + let audio_play = meta_for(Syscall::AudioPlay); + assert_eq!(audio_play.arg_slots, 7); + assert_eq!(audio_play.ret_slots, 1); + + let asset_load = meta_for(Syscall::AssetLoad); + assert_eq!(asset_load.arg_slots, 3); + assert_eq!(asset_load.ret_slots, 2); + + let asset_commit = meta_for(Syscall::AssetCommit); + assert_eq!(asset_commit.arg_slots, 1); + assert_eq!(asset_commit.ret_slots, 1); + + let asset_cancel = meta_for(Syscall::AssetCancel); + assert_eq!(asset_cancel.arg_slots, 1); + assert_eq!(asset_cancel.ret_slots, 1); +} + +#[test] +fn declared_resolver_rejects_legacy_status_first_signatures() { + let declared = vec![ + prometeu_bytecode::SyscallDecl { + module: "gfx".into(), + name: "set_sprite".into(), + version: 1, + arg_slots: 10, + ret_slots: 0, + }, + prometeu_bytecode::SyscallDecl { + module: "audio".into(), + name: "play_sample".into(), + version: 1, + arg_slots: 5, + ret_slots: 0, + }, + prometeu_bytecode::SyscallDecl { + module: "audio".into(), + name: "play".into(), + version: 1, + arg_slots: 7, + ret_slots: 0, + }, + prometeu_bytecode::SyscallDecl { + module: "asset".into(), + name: "load".into(), + version: 1, + arg_slots: 3, + ret_slots: 1, + }, + prometeu_bytecode::SyscallDecl { + module: "asset".into(), + name: "commit".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }, + prometeu_bytecode::SyscallDecl { + module: "asset".into(), + name: "cancel".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }, + ]; + + for decl in declared { + let err = resolve_declared_program_syscalls(std::slice::from_ref(&decl), caps::ALL) + .expect_err("legacy signature must be rejected"); + match err { + DeclaredLoadError::AbiMismatch { + module, + name, + version, + declared_arg_slots, + declared_ret_slots, + expected_arg_slots, + expected_ret_slots, + } => { + assert_eq!(module, decl.module); + assert_eq!(name, decl.name); + assert_eq!(version, decl.version); + assert_eq!(declared_arg_slots, decl.arg_slots); + assert_eq!(declared_ret_slots, decl.ret_slots); + assert_eq!(expected_arg_slots, declared_arg_slots); + assert_ne!(expected_ret_slots, declared_ret_slots); + } + other => panic!("expected AbiMismatch, got {:?}", other), + } + } +} diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index cd6f88c9..68094a2a 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -1259,6 +1259,16 @@ mod tests { .serialize() } + fn serialized_single_hostcall_module(syscall: SyscallDecl) -> Vec { + let mut source = String::new(); + for _ in 0..syscall.arg_slots { + source.push_str("PUSH_I32 0\n"); + } + source.push_str("HOSTCALL 0\nHALT"); + let code = assemble(&source).expect("assemble"); + serialized_single_function_module(code, vec![syscall]) + } + #[test] fn sleep_delays_execution_by_ticks() { let mut native = MockNative; @@ -2863,6 +2873,140 @@ mod tests { ); } + #[test] + fn test_loader_patching_accepts_status_first_signatures() { + let cases = vec![ + SyscallDecl { + module: "gfx".into(), + name: "set_sprite".into(), + version: 1, + arg_slots: 10, + ret_slots: 1, + }, + SyscallDecl { + module: "audio".into(), + name: "play_sample".into(), + version: 1, + arg_slots: 5, + ret_slots: 1, + }, + SyscallDecl { + module: "audio".into(), + name: "play".into(), + version: 1, + arg_slots: 7, + ret_slots: 1, + }, + SyscallDecl { + module: "asset".into(), + name: "load".into(), + version: 1, + arg_slots: 3, + ret_slots: 2, + }, + SyscallDecl { + module: "asset".into(), + name: "commit".into(), + version: 1, + arg_slots: 1, + ret_slots: 1, + }, + SyscallDecl { + module: "asset".into(), + name: "cancel".into(), + version: 1, + arg_slots: 1, + ret_slots: 1, + }, + ]; + + for syscall in cases { + let mut vm = VirtualMachine::default(); + vm.set_capabilities(prometeu_hal::syscalls::caps::ALL); + let bytes = serialized_single_hostcall_module(syscall); + let res = vm.initialize(bytes, ""); + assert!(res.is_ok(), "status-first signature must be accepted: {:?}", res); + } + } + + #[test] + fn test_loader_patching_rejects_legacy_status_first_ret_slots() { + let cases = vec![ + SyscallDecl { + module: "gfx".into(), + name: "set_sprite".into(), + version: 1, + arg_slots: 10, + ret_slots: 0, + }, + SyscallDecl { + module: "audio".into(), + name: "play_sample".into(), + version: 1, + arg_slots: 5, + ret_slots: 0, + }, + SyscallDecl { + module: "audio".into(), + name: "play".into(), + version: 1, + arg_slots: 7, + ret_slots: 0, + }, + SyscallDecl { + module: "asset".into(), + name: "load".into(), + version: 1, + arg_slots: 3, + ret_slots: 1, + }, + SyscallDecl { + module: "asset".into(), + name: "commit".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }, + SyscallDecl { + module: "asset".into(), + name: "cancel".into(), + version: 1, + arg_slots: 1, + ret_slots: 0, + }, + ]; + + for syscall in cases { + let mut vm = VirtualMachine::default(); + vm.set_capabilities(prometeu_hal::syscalls::caps::ALL); + let bytes = serialized_single_hostcall_module(syscall.clone()); + let err = vm.initialize(bytes, "").expect_err("legacy ABI must be rejected"); + match err { + VmInitError::LoaderPatchFailed(crate::vm_init_error::LoaderPatchError::ResolveFailed( + prometeu_hal::syscalls::DeclaredLoadError::AbiMismatch { + module, + name, + version, + declared_arg_slots, + declared_ret_slots, + expected_arg_slots, + expected_ret_slots, + }, + )) => { + assert_eq!(module, syscall.module); + assert_eq!(name, syscall.name); + assert_eq!(version, syscall.version); + assert_eq!(declared_arg_slots, syscall.arg_slots); + assert_eq!(declared_ret_slots, syscall.ret_slots); + + assert_eq!(expected_arg_slots, declared_arg_slots); + assert_ne!(expected_ret_slots, declared_ret_slots); + } + other => panic!("expected ABI mismatch, got {:?}", other), + } + } + } + #[test] fn test_loader_hardening_missing_sysc_section() { let mut vm = VirtualMachine::default(); diff --git a/crates/tools/pbxgen-stress/src/lib.rs b/crates/tools/pbxgen-stress/src/lib.rs index 0f4f0d27..a019a73f 100644 --- a/crates/tools/pbxgen-stress/src/lib.rs +++ b/crates/tools/pbxgen-stress/src/lib.rs @@ -41,6 +41,13 @@ pub fn generate() -> Result<()> { arg_slots: 2, ret_slots: 0, }, + SyscallDecl { + module: "gfx".into(), + name: "set_sprite".into(), + version: 1, + arg_slots: 10, + ret_slots: 1, + }, ]; heavy_load(&mut rom); @@ -60,6 +67,7 @@ pub fn generate() -> Result<()> { const_pool: vec![ ConstantPoolEntry::String("stress".into()), ConstantPoolEntry::String("frame".into()), + ConstantPoolEntry::String("missing_tile_bank".into()), ], functions, code: rom, @@ -100,7 +108,7 @@ fn heavy_load(rom: &mut Vec) { // Loop: // t = (t + 1) // clear screen - // draw 100 discs using t for animation + // draw 500 discs using t for animation // draw 20 texts using t for animation // RET (runtime handles the frame loop) @@ -115,6 +123,10 @@ fn heavy_load(rom: &mut Vec) { // --- clear screen --- rom.extend(asm("PUSH_I32 0\nHOSTCALL 0")); + // --- call status-first syscall path once per frame and drop status --- + rom.extend(asm( + "PUSH_CONST 2\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 4\nPOP_N 1", + )); // --- draw 500 discs --- rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1")); @@ -145,7 +157,7 @@ fn heavy_load(rom: &mut Vec) { // --- draw 20 texts --- rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1")); let text_loop_start = rom.len() as u32; - rom.extend(asm("GET_LOCAL 1\nPUSH_I32 0\nLT")); + rom.extend(asm("GET_LOCAL 1\nPUSH_I32 20\nLT")); let jif_text_end_offset = rom.len() + 2; rom.extend(asm("JMP_IF_FALSE 0")); diff --git a/test-cartridges/stress-console/program.pbx b/test-cartridges/stress-console/program.pbx index d427e77c8e4398a791785f5b1298b9fc0846f243..c5b08acfcea3d889ed89b481732e37aed41cfa58 100644 GIT binary patch delta 327 zcmdnVwv>H>M!h}*0|O%vPX}TVAZ7yM)j*sMWH1A1As_(~X8~e0AX@-PvjVXhkev^t zL26jofJAXgQEG893rH@lC^0vcMG(l!%`7g?%uA0i$;?TOPfEl4nev`0A`uGhDMX*gsBSQXK4dBcLlynI_+4 z4C8$XbO6W;AiLjBwqf#?Ed+`1)C~qeP05p&FxfJ)PJYA`F3!x5o>sxd UP@Gy4UtCa>S(3`gzy-7e00vJg#Q*>R delta 175 zcmZ3=zLRZ&Mv^200|O%vHv+K;5HkVsR3O#>GMIt%ULXw;X941iK(+voW(DGhKz0O> z22uBgdp7AWB%Vc>*aW0UA5zzc^ qjFa`4!gyB!6@v^08Mk5bL?&-Wqse!fq#0Ev|75adWSMNv91Z}&IU5fE