dev/pbs #8

Merged
bquarkz merged 74 commits from dev/pbs into master 2026-02-03 15:28:31 +00:00
8 changed files with 242 additions and 28 deletions
Showing only changes of commit 180c7e19e0 - Show all commits

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,6 +29,7 @@ pub enum Type {
Null,
Bool,
Int,
Bounded,
Float,
String,
Color,

View File

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

View File

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