diff --git a/crates/prometeu-bytecode/src/abi.rs b/crates/prometeu-bytecode/src/abi.rs index 95cae37c..570e78e1 100644 --- a/crates/prometeu-bytecode/src/abi.rs +++ b/crates/prometeu-bytecode/src/abi.rs @@ -11,6 +11,7 @@ pub fn operand_size(opcode: OpCode) -> usize { match opcode { OpCode::PushConst => 4, OpCode::PushI32 => 4, + OpCode::PushBounded => 4, OpCode::PushI64 => 8, OpCode::PushF64 => 8, OpCode::PushBool => 1, diff --git a/crates/prometeu-bytecode/src/disasm.rs b/crates/prometeu-bytecode/src/disasm.rs index 91d655bf..79055a06 100644 --- a/crates/prometeu-bytecode/src/disasm.rs +++ b/crates/prometeu-bytecode/src/disasm.rs @@ -39,7 +39,7 @@ pub fn disasm(rom: &[u8]) -> Result, String> { let mut operands = Vec::new(); match opcode { - OpCode::PushConst | OpCode::PushI32 | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue + OpCode::PushConst | OpCode::PushI32 | OpCode::PushBounded | OpCode::Jmp | OpCode::JmpIfFalse | OpCode::JmpIfTrue | OpCode::GetGlobal | OpCode::SetGlobal | OpCode::GetLocal | OpCode::SetLocal | OpCode::PopN | OpCode::Syscall | OpCode::GateLoad | OpCode::GateStore => { let v = read_u32_le(&mut cursor).map_err(|e| e.to_string())?; diff --git a/crates/prometeu-bytecode/src/opcode.rs b/crates/prometeu-bytecode/src/opcode.rs index 34bce8d8..f366fa83 100644 --- a/crates/prometeu-bytecode/src/opcode.rs +++ b/crates/prometeu-bytecode/src/opcode.rs @@ -56,6 +56,10 @@ pub enum OpCode { /// Removes `n` values from the stack. /// Operand: n (u32) PopN = 0x18, + /// Pushes a 16-bit bounded integer literal onto the stack. + /// Operand: value (u32, must be <= 0xFFFF) + /// Stack: [] -> [bounded] + PushBounded = 0x19, // --- 6.3 Arithmetic --- @@ -225,6 +229,7 @@ impl TryFrom for OpCode { 0x16 => Ok(OpCode::PushBool), 0x17 => Ok(OpCode::PushI32), 0x18 => Ok(OpCode::PopN), + 0x19 => Ok(OpCode::PushBounded), 0x20 => Ok(OpCode::Add), 0x21 => Ok(OpCode::Sub), 0x22 => Ok(OpCode::Mul), @@ -290,6 +295,7 @@ impl OpCode { OpCode::PushF64 => 2, OpCode::PushBool => 2, OpCode::PushI32 => 2, + OpCode::PushBounded => 2, OpCode::Add => 2, OpCode::Sub => 2, OpCode::Mul => 4, diff --git a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs index c426fb20..434f91d3 100644 --- a/crates/prometeu-compiler/src/frontends/pbs/lowering.rs +++ b/crates/prometeu-compiler/src/frontends/pbs/lowering.rs @@ -7,6 +7,12 @@ use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId}; use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type}; use std::collections::HashMap; +#[derive(Clone)] +struct LocalInfo { + slot: u32, + ty: Type, +} + pub struct Lowerer<'a> { module_symbols: &'a ModuleSymbols, program: Program, @@ -15,7 +21,7 @@ pub struct Lowerer<'a> { next_block_id: u32, next_func_id: u32, next_type_id: u32, - local_vars: Vec>, + local_vars: Vec>, function_ids: HashMap, type_ids: HashMap, struct_slots: HashMap, @@ -28,6 +34,12 @@ impl<'a> Lowerer<'a> { let mut field_offsets = HashMap::new(); field_offsets.insert(FieldId(0), 0); // V0 hardcoded field resolution foundation + let mut struct_slots = HashMap::new(); + struct_slots.insert("Color".to_string(), 1); + struct_slots.insert("ButtonState".to_string(), 4); + struct_slots.insert("Pad".to_string(), 48); + struct_slots.insert("Touch".to_string(), 6); + Self { module_symbols, program: Program { @@ -44,7 +56,7 @@ impl<'a> Lowerer<'a> { local_vars: Vec::new(), function_ids: HashMap::new(), type_ids: HashMap::new(), - struct_slots: HashMap::new(), + struct_slots, contract_registry: ContractRegistry::new(), diagnostics: Vec::new(), } @@ -342,9 +354,32 @@ impl<'a> Lowerer<'a> { fn lower_let_stmt(&mut self, n: &LetStmtNode) -> Result<(), ()> { self.lower_node(&n.init)?; + + let ty = if let Some(ty_node) = &n.ty { + self.lower_type_node(ty_node) + } else { + // Very basic inference for host calls + if let Node::Call(call) = &*n.init { + if let Node::MemberAccess(ma) = &*call.callee { + if let Node::Ident(obj) = &*ma.object { + match (obj.name.as_str(), ma.member.as_str()) { + ("Input", "pad") => Type::Struct("Pad".to_string()), + ("Input", "touch") => Type::Struct("Touch".to_string()), + _ => Type::Int, + } + } else { Type::Int } + } else { Type::Int } + } else { Type::Int } + }; + let slot = self.get_next_local_slot(); - self.local_vars.last_mut().unwrap().insert(n.name.clone(), slot); - self.emit(Instr::SetLocal(slot)); + let slots = self.get_type_slots(&ty); + + self.local_vars.last_mut().unwrap().insert(n.name.clone(), LocalInfo { slot, ty }); + + for i in (0..slots).rev() { + self.emit(Instr::SetLocal(slot + i)); + } Ok(()) } @@ -357,8 +392,11 @@ impl<'a> Lowerer<'a> { } fn lower_ident(&mut self, n: &IdentNode) -> Result<(), ()> { - if let Some(slot) = self.lookup_local(&n.name) { - self.emit(Instr::GetLocal(slot)); + if let Some(info) = self.find_local(&n.name) { + let slots = self.get_type_slots(&info.ty); + for i in 0..slots { + self.emit(Instr::GetLocal(info.slot + i)); + } Ok(()) } else { // Check for special identifiers @@ -407,8 +445,8 @@ impl<'a> Lowerer<'a> { "TRANSPARENT" => 0x0000, "COLOR_KEY" => 0x0000, _ => { - self.error("E_RESOLVE_UNDEFINED", format!("Undefined Color constant '{}'", n.member), n.span); - return Err(()); + // Check if it's a method call like Color.rgb, handled in lower_call + return Ok(()); } }; let id = self.program.const_pool.add_int(val); @@ -417,18 +455,94 @@ impl<'a> Lowerer<'a> { } } - // For instance members (e.g., p.any), we'll just ignore for now in v0 - // to pass typecheck tests if they are being lowered. - // In a real implementation we would need type information. + if let Some((slot, ty)) = self.resolve_member_access(n) { + let slots = self.get_type_slots(&ty); + for i in 0..slots { + self.emit(Instr::GetLocal(slot + i)); + } + return Ok(()); + } + Ok(()) } - fn lower_call(&mut self, n: &CallNode) -> Result<(), ()> { - for arg in &n.args { - self.lower_node(arg)?; + fn resolve_member_access(&self, n: &MemberAccessNode) -> Option<(u32, Type)> { + match &*n.object { + Node::Ident(id) => { + let info = self.find_local(&id.name)?; + if let Type::Struct(sname) = &info.ty { + let offset = self.get_field_offset(sname, &n.member); + let ty = self.get_field_type(sname, &n.member); + Some((info.slot + offset, ty)) + } else { None } + } + Node::MemberAccess(inner) => { + let (base_slot, ty) = self.resolve_member_access(inner)?; + if let Type::Struct(sname) = &ty { + let offset = self.get_field_offset(sname, &n.member); + let final_ty = self.get_field_type(sname, &n.member); + Some((base_slot + offset, final_ty)) + } else { None } + } + _ => None } + } + + fn get_field_offset(&self, struct_name: &str, field_name: &str) -> u32 { + match struct_name { + "ButtonState" => match field_name { + "pressed" => 0, + "released" => 1, + "down" => 2, + "hold_frames" => 3, + _ => 0, + }, + "Pad" => match field_name { + "up" => 0, + "down" => 4, + "left" => 8, + "right" => 12, + "a" => 16, + "b" => 20, + "x" => 24, + "y" => 28, + "l" => 32, + "r" => 36, + "start" => 40, + "select" => 44, + _ => 0, + }, + "Touch" => match field_name { + "f" => 0, + "x" => 4, + "y" => 5, + _ => 0, + }, + _ => 0, + } + } + + fn get_field_type(&self, struct_name: &str, field_name: &str) -> Type { + match struct_name { + "Pad" => Type::Struct("ButtonState".to_string()), + "ButtonState" => match field_name { + "hold_frames" => Type::Bounded, + _ => Type::Bool, + }, + "Touch" => match field_name { + "f" => Type::Struct("ButtonState".to_string()), + _ => Type::Int, + }, + _ => Type::Int, + } + } + + fn lower_call(&mut self, n: &CallNode) -> Result<(), ()> { match &*n.callee { Node::Ident(id_node) => { + for arg in &n.args { + self.lower_node(arg)?; + } if let Some(func_id) = self.function_ids.get(&id_node.name) { self.emit(Instr::Call(*func_id, n.args.len() as u32)); Ok(()) @@ -436,9 +550,6 @@ impl<'a> Lowerer<'a> { // Check for special built-in functions match id_node.name.as_str() { "some" | "ok" | "err" => { - // For now, these are effectively nops in terms of IR emission, - // as they just wrap the already pushed arguments. - // In a real implementation, they might push a tag. return Ok(()); } _ => {} @@ -449,14 +560,59 @@ impl<'a> Lowerer<'a> { } } Node::MemberAccess(ma) => { + // Check for Pad.any() + if ma.member == "any" { + if let Node::Ident(obj_id) = &*ma.object { + if let Some(info) = self.find_local(&obj_id.name) { + if let Type::Struct(sname) = &info.ty { + if sname == "Pad" { + self.lower_pad_any(info.slot); + return Ok(()); + } + } + } + } + } + + // Check for Color.rgb + if ma.member == "rgb" { + if let Node::Ident(obj_id) = &*ma.object { + if obj_id.name == "Color" { + if n.args.len() == 3 { + // Try to get literal values for r, g, b + let mut literals = Vec::new(); + for arg in &n.args { + if let Node::IntLiteral(lit) = arg { + literals.push(Some(lit.value)); + } else { + literals.push(None); + } + } + + if let (Some(r), Some(g), Some(b)) = (literals[0], literals[1], literals[2]) { + let r5 = (r & 0xFF) >> 3; + let g6 = (g & 0xFF) >> 2; + let b5 = (b & 0xFF) >> 3; + let rgb565 = (r5 << 11) | (g6 << 5) | b5; + let id = self.program.const_pool.add_int(rgb565); + self.emit(Instr::PushConst(id)); + return Ok(()); + } + } + } + } + } + + for arg in &n.args { + self.lower_node(arg)?; + } + if let Node::Ident(obj_id) = &*ma.object { - // Check if it's a host contract according to symbol table let is_host_contract = self.module_symbols.type_symbols.get(&obj_id.name) .map(|sym| sym.kind == SymbolKind::Contract && sym.is_host) .unwrap_or(false); - // Ensure it's not shadowed by a local variable - let is_shadowed = self.lookup_local(&obj_id.name).is_some(); + let is_shadowed = self.find_local(&obj_id.name).is_some(); if is_host_contract && !is_shadowed { if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) { @@ -469,18 +625,33 @@ impl<'a> Lowerer<'a> { } } - // Regular member call (method) or fallback - // In v0 we don't handle this yet. self.error("E_LOWER_UNSUPPORTED", "Method calls not supported in v0".to_string(), ma.span); Err(()) } _ => { + for arg in &n.args { + self.lower_node(arg)?; + } self.error("E_LOWER_UNSUPPORTED", "Indirect calls not supported in v0".to_string(), n.callee.span()); Err(()) } } } + fn lower_pad_any(&mut self, base_slot: u32) { + for i in 0..12 { + let btn_base = base_slot + (i * 4); + self.emit(Instr::GetLocal(btn_base)); // pressed + self.emit(Instr::GetLocal(btn_base + 1)); // released + self.emit(Instr::Or); + self.emit(Instr::GetLocal(btn_base + 2)); // down + self.emit(Instr::Or); + if i > 0 { + self.emit(Instr::Or); + } + } + } + fn lower_binary(&mut self, n: &BinaryNode) -> Result<(), ()> { self.lower_node(&n.left)?; self.lower_node(&n.right)?; @@ -550,6 +721,7 @@ impl<'a> Lowerer<'a> { match node { Node::TypeName(n) => match n.name.as_str() { "int" => Type::Int, + "bounded" => Type::Bounded, "float" => Type::Float, "bool" => Type::Bool, "string" => Type::String, @@ -624,17 +796,25 @@ impl<'a> Lowerer<'a> { } fn get_next_local_slot(&self) -> u32 { - self.local_vars.iter().map(|s| s.len() as u32).sum() + self.local_vars.iter().flat_map(|s| s.values()).map(|info| self.get_type_slots(&info.ty)).sum() } - fn lookup_local(&self, name: &str) -> Option { + fn find_local(&self, name: &str) -> Option { for scope in self.local_vars.iter().rev() { - if let Some(slot) = scope.get(name) { - return Some(*slot); + if let Some(info) = scope.get(name) { + return Some(info.clone()); } } None } + + fn get_type_slots(&self, ty: &Type) -> u32 { + match ty { + Type::Struct(name) => self.struct_slots.get(name).cloned().unwrap_or(1), + Type::Array(_, size) => *size, + _ => 1, + } + } } #[cfg(test)] diff --git a/crates/prometeu-compiler/src/ir_core/types.rs b/crates/prometeu-compiler/src/ir_core/types.rs index 882e4076..86aa8e95 100644 --- a/crates/prometeu-compiler/src/ir_core/types.rs +++ b/crates/prometeu-compiler/src/ir_core/types.rs @@ -5,6 +5,7 @@ use std::fmt; pub enum Type { Void, Int, + Bounded, Float, Bool, String, @@ -26,6 +27,7 @@ impl fmt::Display for Type { match self { Type::Void => write!(f, "void"), Type::Int => write!(f, "int"), + Type::Bounded => write!(f, "bounded"), Type::Float => write!(f, "float"), Type::Bool => write!(f, "bool"), Type::String => write!(f, "string"), diff --git a/crates/prometeu-compiler/src/ir_vm/types.rs b/crates/prometeu-compiler/src/ir_vm/types.rs index 97bf58a6..07d35620 100644 --- a/crates/prometeu-compiler/src/ir_vm/types.rs +++ b/crates/prometeu-compiler/src/ir_vm/types.rs @@ -29,6 +29,7 @@ pub enum Type { Null, Bool, Int, + Bounded, Float, String, Color, diff --git a/crates/prometeu-core/src/hardware/syscalls.rs b/crates/prometeu-core/src/hardware/syscalls.rs index 0e91970e..2a733ca8 100644 --- a/crates/prometeu-core/src/hardware/syscalls.rs +++ b/crates/prometeu-core/src/hardware/syscalls.rs @@ -37,6 +37,8 @@ pub enum Syscall { GfxSetSprite = 0x1007, /// Draws a text string at the specified coordinates. GfxDrawText = 0x1008, + /// Fills the entire back buffer with a single RGB565 color (flattened). + GfxClear565 = 0x1010, // --- Input --- /// Returns the current raw state of the digital gamepad (bitmask). @@ -47,6 +49,10 @@ pub enum Syscall { InputGetPadReleased = 0x2003, /// Returns how many frames a button has been held down. InputGetPadHold = 0x2004, + /// Returns the full snapshot of the gamepad state (48 slots). + InputPadSnapshot = 0x2010, + /// Returns the full snapshot of the touch state (6 slots). + InputTouchSnapshot = 0x2011, /// Returns the X coordinate of the touch/mouse pointer. TouchGetX = 0x2101, @@ -119,10 +125,13 @@ impl Syscall { 0x1006 => Some(Self::GfxDrawSquare), 0x1007 => Some(Self::GfxSetSprite), 0x1008 => Some(Self::GfxDrawText), + 0x1010 => Some(Self::GfxClear565), 0x2001 => Some(Self::InputGetPad), 0x2002 => Some(Self::InputGetPadPressed), 0x2003 => Some(Self::InputGetPadReleased), 0x2004 => Some(Self::InputGetPadHold), + 0x2010 => Some(Self::InputPadSnapshot), + 0x2011 => Some(Self::InputTouchSnapshot), 0x2101 => Some(Self::TouchGetX), 0x2102 => Some(Self::TouchGetY), 0x2103 => Some(Self::TouchIsDown), @@ -162,10 +171,13 @@ impl Syscall { Self::GfxDrawSquare => 6, Self::GfxSetSprite => 10, Self::GfxDrawText => 4, + Self::GfxClear565 => 1, Self::InputGetPad => 1, Self::InputGetPadPressed => 1, Self::InputGetPadReleased => 1, Self::InputGetPadHold => 1, + Self::InputPadSnapshot => 0, + Self::InputTouchSnapshot => 0, Self::TouchGetX => 0, Self::TouchGetY => 0, Self::TouchIsDown => 0, diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index 8a51808c..91258b87 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -5,7 +5,7 @@ use crate::virtual_machine::value::Value; use crate::virtual_machine::{NativeInterface, Program}; use prometeu_bytecode::opcode::OpCode; use prometeu_bytecode::pbc::{self, ConstantPoolEntry}; -use prometeu_bytecode::abi::TrapInfo; +use prometeu_bytecode::abi::{TrapInfo, TRAP_OOB}; /// Reason why the Virtual Machine stopped execution during a specific run. /// This allows the system to decide if it should continue execution in the next tick @@ -329,6 +329,18 @@ impl VirtualMachine { let val = self.read_i32().map_err(|e| LogicalFrameEndingReason::Panic(e))?; self.push(Value::Int32(val)); } + OpCode::PushBounded => { + let val = self.read_u32().map_err(|e| LogicalFrameEndingReason::Panic(e))?; + if val > 0xFFFF { + return Err(LogicalFrameEndingReason::Trap(TrapInfo { + code: TRAP_OOB, + opcode: opcode as u16, + message: format!("Bounded value overflow: {} > 0xFFFF", val), + pc: start_pc as u32, + })); + } + self.push(Value::Bounded(val)); + } OpCode::PushF64 => { let val = self.read_f64().map_err(|e| LogicalFrameEndingReason::Panic(e))?; self.push(Value::Float(val));