PR005: sync ABI verifier/loader and regenerate stress cartridge
This commit is contained in:
parent
c8b3b6afcc
commit
70ffadab6c
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1259,6 +1259,16 @@ mod tests {
|
||||
.serialize()
|
||||
}
|
||||
|
||||
fn serialized_single_hostcall_module(syscall: SyscallDecl) -> Vec<u8> {
|
||||
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();
|
||||
|
||||
@ -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<u8>) {
|
||||
// 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<u8>) {
|
||||
|
||||
// --- 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<u8>) {
|
||||
// --- 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"));
|
||||
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user