fix compiler issues
This commit is contained in:
parent
926ad2a807
commit
e2a5a08bf2
@ -259,7 +259,8 @@ mod tests {
|
||||
00BE GateRelease
|
||||
00C0 GetLocal U32(2)
|
||||
00C6 GateRelease
|
||||
00C8 Ret
|
||||
00C8 PushConst U32(0)
|
||||
00CE Ret
|
||||
"#;
|
||||
|
||||
assert_eq!(disasm_text, expected_disasm);
|
||||
|
||||
@ -146,7 +146,7 @@ mod tests {
|
||||
assert_eq!(func.name, "start");
|
||||
assert_eq!(func.id, FunctionId(10));
|
||||
|
||||
assert_eq!(func.body.len(), 3);
|
||||
assert_eq!(func.body.len(), 4);
|
||||
match &func.body[0].kind {
|
||||
InstrKind::Label(Label(l)) => assert!(l.contains("block_0")),
|
||||
_ => panic!("Expected label"),
|
||||
@ -156,6 +156,10 @@ mod tests {
|
||||
_ => panic!("Expected PushConst"),
|
||||
}
|
||||
match &func.body[2].kind {
|
||||
InstrKind::PushNull => (),
|
||||
_ => panic!("Expected PushNull"),
|
||||
}
|
||||
match &func.body[3].kind {
|
||||
InstrKind::Ret => (),
|
||||
_ => panic!("Expected Ret"),
|
||||
}
|
||||
|
||||
@ -313,6 +313,13 @@ pub fn lower_function(
|
||||
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::GateRelease, None));
|
||||
}
|
||||
}
|
||||
|
||||
// If the function is Void, we must push a Null value to satisfy the VM's RET instruction.
|
||||
// The VM always pops one value from the stack to be the return value.
|
||||
if vm_func.return_type == ir_vm::Type::Void {
|
||||
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushNull, None));
|
||||
}
|
||||
|
||||
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Ret, None));
|
||||
}
|
||||
ir_core::Terminator::Jump(target) => {
|
||||
@ -422,7 +429,7 @@ mod tests {
|
||||
let func = &vm_module.functions[0];
|
||||
assert_eq!(func.name, "main");
|
||||
|
||||
assert_eq!(func.body.len(), 7);
|
||||
assert_eq!(func.body.len(), 8);
|
||||
|
||||
match &func.body[0].kind {
|
||||
InstrKind::Label(Label(l)) => assert_eq!(l, "block_0"),
|
||||
@ -452,6 +459,10 @@ mod tests {
|
||||
_ => panic!("Expected HostCall 42"),
|
||||
}
|
||||
match &func.body[6].kind {
|
||||
InstrKind::PushNull => (),
|
||||
_ => panic!("Expected PushNull"),
|
||||
}
|
||||
match &func.body[7].kind {
|
||||
InstrKind::Ret => (),
|
||||
_ => panic!("Expected Ret"),
|
||||
}
|
||||
@ -500,7 +511,7 @@ mod tests {
|
||||
// GateStore 100 (offset)
|
||||
// Ret
|
||||
|
||||
assert_eq!(func.body.len(), 9);
|
||||
assert_eq!(func.body.len(), 10);
|
||||
match &func.body[1].kind {
|
||||
ir_vm::InstrKind::LocalLoad { slot } => assert_eq!(*slot, 0),
|
||||
_ => panic!("Expected LocalLoad 0"),
|
||||
@ -517,6 +528,14 @@ mod tests {
|
||||
ir_vm::InstrKind::GateStore { offset } => assert_eq!(*offset, 100),
|
||||
_ => panic!("Expected GateStore 100"),
|
||||
}
|
||||
match &func.body[8].kind {
|
||||
ir_vm::InstrKind::PushNull => (),
|
||||
_ => panic!("Expected PushNull"),
|
||||
}
|
||||
match &func.body[9].kind {
|
||||
ir_vm::InstrKind::Ret => (),
|
||||
_ => panic!("Expected Ret"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -609,10 +628,10 @@ mod tests {
|
||||
assert!(found_overwrite, "Should have emitted release-then-store sequence for overwrite");
|
||||
|
||||
// Check Ret cleanup:
|
||||
// LocalLoad 1, GateRelease, Ret
|
||||
// LocalLoad 1, GateRelease, PushNull, Ret
|
||||
let mut found_cleanup = false;
|
||||
for i in 0..kinds.len() - 2 {
|
||||
if let (InstrKind::LocalLoad { slot: 1 }, InstrKind::GateRelease, InstrKind::Ret) = (kinds[i], kinds[i+1], kinds[i+2]) {
|
||||
for i in 0..kinds.len() - 3 {
|
||||
if let (InstrKind::LocalLoad { slot: 1 }, InstrKind::GateRelease, InstrKind::PushNull, InstrKind::Ret) = (kinds[i], kinds[i+1], kinds[i+2], kinds[i+3]) {
|
||||
found_cleanup = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -92,7 +92,7 @@ fn test_hip_conformance_core_to_vm_to_bytecode() {
|
||||
0x02, 0x00, 0x00, 0x00, // CP Count: 2
|
||||
0x00, // CP[0]: Null
|
||||
0x01, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // CP[1]: Int64(42)
|
||||
0x66, 0x00, 0x00, 0x00, // ROM Size: 102
|
||||
0x6c, 0x00, 0x00, 0x00, // ROM Size: 108
|
||||
// Instructions:
|
||||
0x60, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, // Alloc { tid: 10, slots: 2 }
|
||||
0x43, 0x00, 0x00, 0x00, 0x00, 0x00, // SetLocal 0
|
||||
@ -118,6 +118,7 @@ fn test_hip_conformance_core_to_vm_to_bytecode() {
|
||||
0x11, 0x00, // Pop
|
||||
0x42, 0x00, 0x00, 0x00, 0x00, 0x00, // GetLocal 0 (cleanup)
|
||||
0x6a, 0x00, // GateRelease
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // PushConst 0 (Null return)
|
||||
0x51, 0x00, // Ret
|
||||
];
|
||||
|
||||
|
||||
@ -51,6 +51,7 @@ pub struct PrometeuOS {
|
||||
pub current_cartridge_title: String,
|
||||
pub current_cartridge_app_version: String,
|
||||
pub current_cartridge_app_mode: crate::model::AppMode,
|
||||
pub current_entrypoint: String,
|
||||
/// Rate-limiter to prevent apps from flooding the log buffer and killing performance.
|
||||
pub logs_written_this_frame: HashMap<u32, u32>,
|
||||
|
||||
@ -100,6 +101,7 @@ impl PrometeuOS {
|
||||
current_cartridge_title: String::new(),
|
||||
current_cartridge_app_version: String::new(),
|
||||
current_cartridge_app_mode: crate::model::AppMode::Game,
|
||||
current_entrypoint: String::new(),
|
||||
logs_written_this_frame: HashMap::new(),
|
||||
telemetry_current: TelemetryFrame::default(),
|
||||
telemetry_last: TelemetryFrame::default(),
|
||||
@ -167,6 +169,7 @@ impl PrometeuOS {
|
||||
self.current_cartridge_title = cartridge.title.clone();
|
||||
self.current_cartridge_app_version = cartridge.app_version.clone();
|
||||
self.current_cartridge_app_mode = cartridge.app_mode;
|
||||
self.current_entrypoint = cartridge.entrypoint.clone();
|
||||
}
|
||||
|
||||
/// Executes a single VM instruction (Debug).
|
||||
@ -204,6 +207,12 @@ impl PrometeuOS {
|
||||
self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME;
|
||||
self.begin_logical_frame(signals, hw);
|
||||
|
||||
// If the VM is not currently executing a function (e.g. at the start of the app
|
||||
// or after the entrypoint function returned), we prepare a new call to the entrypoint.
|
||||
if vm.call_stack.is_empty() {
|
||||
vm.prepare_call(&self.current_entrypoint);
|
||||
}
|
||||
|
||||
// Reset telemetry for the new logical frame
|
||||
self.telemetry_current = TelemetryFrame {
|
||||
frame_index: self.logical_frame_index,
|
||||
@ -236,8 +245,16 @@ impl PrometeuOS {
|
||||
self.log(LogLevel::Info, LogSource::Vm, 0xDEB1, format!("Breakpoint hit at PC 0x{:X}", vm.pc));
|
||||
}
|
||||
|
||||
// 4. Frame Finalization (FRAME_SYNC reached)
|
||||
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync {
|
||||
// Handle Panics
|
||||
if let crate::virtual_machine::LogicalFrameEndingReason::Panic(err) = run.reason {
|
||||
let err_msg = format!("PVM Fault: \"{}\"", err);
|
||||
self.log(LogLevel::Error, LogSource::Vm, 0, err_msg.clone());
|
||||
return Some(err_msg);
|
||||
}
|
||||
|
||||
// 4. Frame Finalization (FRAME_SYNC reached or Entrypoint returned)
|
||||
if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync ||
|
||||
run.reason == crate::virtual_machine::LogicalFrameEndingReason::EndOfRom {
|
||||
// All drawing commands for this frame are now complete.
|
||||
// Finalize the framebuffer.
|
||||
hw.gfx_mut().render_all();
|
||||
@ -626,6 +643,45 @@ mod tests {
|
||||
os.syscall(0x1001, &mut vm, &mut hw).unwrap(); // gfx.clear
|
||||
assert_eq!(vm.pop().unwrap(), Value::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entrypoint_called_every_frame() {
|
||||
let mut os = PrometeuOS::new(None);
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hw = Hardware::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
// PushI32 0 (0x17), then Ret (0x51)
|
||||
let rom = vec![
|
||||
0x17, 0x00, // PushI32
|
||||
0x00, 0x00, 0x00, 0x00, // value 0
|
||||
0x51, 0x00 // Ret
|
||||
];
|
||||
let cartridge = Cartridge {
|
||||
app_id: 1234,
|
||||
title: "test".to_string(),
|
||||
app_version: "1.0.0".to_string(),
|
||||
app_mode: AppMode::Game,
|
||||
entrypoint: "0".to_string(),
|
||||
program: rom,
|
||||
assets: vec![],
|
||||
asset_table: vec![],
|
||||
preload: vec![],
|
||||
};
|
||||
os.initialize_vm(&mut vm, &cartridge);
|
||||
|
||||
// First frame
|
||||
os.tick(&mut vm, &signals, &mut hw);
|
||||
assert_eq!(os.logical_frame_index, 1);
|
||||
assert!(!os.logical_frame_active);
|
||||
assert!(vm.call_stack.is_empty());
|
||||
|
||||
// Second frame - Should call entrypoint again
|
||||
os.tick(&mut vm, &signals, &mut hw);
|
||||
assert_eq!(os.logical_frame_index, 2);
|
||||
assert!(!os.logical_frame_active);
|
||||
assert!(vm.call_stack.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeInterface for PrometeuOS {
|
||||
|
||||
@ -137,6 +137,30 @@ impl VirtualMachine {
|
||||
self.cycles = 0;
|
||||
self.halted = false;
|
||||
}
|
||||
|
||||
/// Prepares the VM to execute a specific entrypoint by setting the PC and
|
||||
/// pushing an initial call frame.
|
||||
pub fn prepare_call(&mut self, entrypoint: &str) {
|
||||
let addr = if let Ok(addr) = entrypoint.parse::<usize>() {
|
||||
addr
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
self.pc = addr;
|
||||
self.halted = false;
|
||||
|
||||
// Pushing a sentinel frame so RET works at the top level.
|
||||
// The return address is set to the end of ROM, which will naturally
|
||||
// cause the VM to stop after returning from the entrypoint.
|
||||
self.operand_stack.clear();
|
||||
self.call_stack.clear();
|
||||
self.scope_stack.clear();
|
||||
self.call_stack.push(CallFrame {
|
||||
return_pc: self.program.rom.len() as u32,
|
||||
stack_base: 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for VirtualMachine {
|
||||
@ -1277,4 +1301,25 @@ mod tests {
|
||||
_ => panic!("Expected Trap, got {:?}", report.reason),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entry_point_ret_with_prepare_call() {
|
||||
// PushI32 0 (0x17), then Ret (0x51)
|
||||
let rom = vec![
|
||||
0x17, 0x00, // PushI32
|
||||
0x00, 0x00, 0x00, 0x00, // value 0
|
||||
0x51, 0x00 // Ret
|
||||
];
|
||||
let mut vm = VirtualMachine::new(rom, vec![]);
|
||||
let mut hw = crate::Hardware::new();
|
||||
struct TestNative;
|
||||
impl NativeInterface for TestNative {
|
||||
fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine, _hw: &mut dyn HardwareBridge) -> Result<u64, String> { Ok(0) }
|
||||
}
|
||||
let mut native = TestNative;
|
||||
|
||||
vm.prepare_call("0");
|
||||
let result = vm.run_budget(100, &mut native, &mut hw).expect("VM run failed");
|
||||
assert_eq!(result.reason, LogicalFrameEndingReason::EndOfRom);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
00000000 PushConst U32(1)
|
||||
00000006 SetLocal U32(0)
|
||||
0000000C GetLocal U32(0)
|
||||
00000012 PushConst U32(1)
|
||||
00000018 Eq
|
||||
0000001A JmpIfFalse U32(56)
|
||||
00000020 Jmp U32(38)
|
||||
00000026 PushConst U32(2)
|
||||
0000002C Syscall U32(4097)
|
||||
00000032 Jmp U32(62)
|
||||
00000038 Jmp U32(62)
|
||||
0000003E Ret
|
||||
Binary file not shown.
@ -1 +1 @@
|
||||
../../target/debug
|
||||
../../dist-staging/stable/prometeu-aarch64-apple-darwin
|
||||
@ -1,6 +1,2 @@
|
||||
declare contract Touch host {
|
||||
fn f();
|
||||
}
|
||||
|
||||
fn frame(): void {
|
||||
}
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
pub declare contract Gfx host {}
|
||||
pub declare contract Input host {}
|
||||
pub declare contract Touch host {}
|
||||
@ -1,7 +0,0 @@
|
||||
import Gfx from "./sdk.pbs"
|
||||
|
||||
fn frame(): void {
|
||||
Gfx.clear(0);
|
||||
Gfx.fillRect(110, 110, 20, 20, 2016);
|
||||
Gfx.drawText(10, 10, "Test01 PBS - Minimal", 65535);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user