dev/pbs #8
@ -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,
|
||||
|
||||
@ -39,7 +39,7 @@ pub fn disasm(rom: &[u8]) -> Result<Vec<Instr>, 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())?;
|
||||
|
||||
@ -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<u16> 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,
|
||||
|
||||
@ -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<HashMap<String, u32>>,
|
||||
local_vars: Vec<HashMap<String, LocalInfo>>,
|
||||
function_ids: HashMap<String, FunctionId>,
|
||||
type_ids: HashMap<String, TypeId>,
|
||||
struct_slots: HashMap<String, u32>,
|
||||
@ -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<u32> {
|
||||
fn find_local(&self, name: &str) -> Option<LocalInfo> {
|
||||
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)]
|
||||
|
||||
@ -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"),
|
||||
|
||||
@ -29,6 +29,7 @@ pub enum Type {
|
||||
Null,
|
||||
Bool,
|
||||
Int,
|
||||
Bounded,
|
||||
Float,
|
||||
String,
|
||||
Color,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user