fix compiler issues

This commit is contained in:
Nilton Constantino 2026-01-30 17:28:04 +00:00
parent 926ad2a807
commit e2a5a08bf2
No known key found for this signature in database
12 changed files with 137 additions and 37 deletions

View File

@ -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);

View File

@ -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"),
}

View File

@ -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;
}

View File

@ -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
];

View File

@ -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 {

View File

@ -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);
}
}

View File

@ -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

View File

@ -1 +1 @@
../../target/debug
../../dist-staging/stable/prometeu-aarch64-apple-darwin

View File

@ -1,6 +1,2 @@
declare contract Touch host {
fn f();
}
fn frame(): void {
}

View File

@ -1,3 +0,0 @@
pub declare contract Gfx host {}
pub declare contract Input host {}
pub declare contract Touch host {}

View File

@ -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);
}