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()
|
.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]
|
#[test]
|
||||||
fn sleep_delays_execution_by_ticks() {
|
fn sleep_delays_execution_by_ticks() {
|
||||||
let mut native = MockNative;
|
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]
|
#[test]
|
||||||
fn test_loader_hardening_missing_sysc_section() {
|
fn test_loader_hardening_missing_sysc_section() {
|
||||||
let mut vm = VirtualMachine::default();
|
let mut vm = VirtualMachine::default();
|
||||||
|
|||||||
@ -41,6 +41,13 @@ pub fn generate() -> Result<()> {
|
|||||||
arg_slots: 2,
|
arg_slots: 2,
|
||||||
ret_slots: 0,
|
ret_slots: 0,
|
||||||
},
|
},
|
||||||
|
SyscallDecl {
|
||||||
|
module: "gfx".into(),
|
||||||
|
name: "set_sprite".into(),
|
||||||
|
version: 1,
|
||||||
|
arg_slots: 10,
|
||||||
|
ret_slots: 1,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
heavy_load(&mut rom);
|
heavy_load(&mut rom);
|
||||||
@ -60,6 +67,7 @@ pub fn generate() -> Result<()> {
|
|||||||
const_pool: vec![
|
const_pool: vec![
|
||||||
ConstantPoolEntry::String("stress".into()),
|
ConstantPoolEntry::String("stress".into()),
|
||||||
ConstantPoolEntry::String("frame".into()),
|
ConstantPoolEntry::String("frame".into()),
|
||||||
|
ConstantPoolEntry::String("missing_tile_bank".into()),
|
||||||
],
|
],
|
||||||
functions,
|
functions,
|
||||||
code: rom,
|
code: rom,
|
||||||
@ -100,7 +108,7 @@ fn heavy_load(rom: &mut Vec<u8>) {
|
|||||||
// Loop:
|
// Loop:
|
||||||
// t = (t + 1)
|
// t = (t + 1)
|
||||||
// clear screen
|
// clear screen
|
||||||
// draw 100 discs using t for animation
|
// draw 500 discs using t for animation
|
||||||
// draw 20 texts using t for animation
|
// draw 20 texts using t for animation
|
||||||
// RET (runtime handles the frame loop)
|
// RET (runtime handles the frame loop)
|
||||||
|
|
||||||
@ -115,6 +123,10 @@ fn heavy_load(rom: &mut Vec<u8>) {
|
|||||||
|
|
||||||
// --- clear screen ---
|
// --- clear screen ---
|
||||||
rom.extend(asm("PUSH_I32 0\nHOSTCALL 0"));
|
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 ---
|
// --- draw 500 discs ---
|
||||||
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
||||||
@ -145,7 +157,7 @@ fn heavy_load(rom: &mut Vec<u8>) {
|
|||||||
// --- draw 20 texts ---
|
// --- draw 20 texts ---
|
||||||
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
rom.extend(asm("PUSH_I32 0\nSET_LOCAL 1"));
|
||||||
let text_loop_start = rom.len() as u32;
|
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;
|
let jif_text_end_offset = rom.len() + 2;
|
||||||
rom.extend(asm("JMP_IF_FALSE 0"));
|
rom.extend(asm("JMP_IF_FALSE 0"));
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user