pr 40
This commit is contained in:
parent
180c7e19e0
commit
adcb34826c
@ -103,6 +103,9 @@ impl<'a> BytecodeEmitter<'a> {
|
|||||||
let mapped_id = mapped_const_ids[id.0 as usize];
|
let mapped_id = mapped_const_ids[id.0 as usize];
|
||||||
asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(mapped_id)]));
|
asm_instrs.push(Asm::Op(OpCode::PushConst, vec![Operand::U32(mapped_id)]));
|
||||||
}
|
}
|
||||||
|
InstrKind::PushBounded(val) => {
|
||||||
|
asm_instrs.push(Asm::Op(OpCode::PushBounded, vec![Operand::U32(*val)]));
|
||||||
|
}
|
||||||
InstrKind::PushBool(v) => {
|
InstrKind::PushBool(v) => {
|
||||||
asm_instrs.push(Asm::Op(OpCode::PushBool, vec![Operand::Bool(*v)]));
|
asm_instrs.push(Asm::Op(OpCode::PushBool, vec![Operand::Bool(*v)]));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -224,7 +224,7 @@ mod tests {
|
|||||||
000C Mul
|
000C Mul
|
||||||
000E Ret
|
000E Ret
|
||||||
0010 PushConst U32(2)
|
0010 PushConst U32(2)
|
||||||
0016 Syscall U32(4097)
|
0016 Syscall U32(4112)
|
||||||
001C PushConst U32(3)
|
001C PushConst U32(3)
|
||||||
0022 SetLocal U32(0)
|
0022 SetLocal U32(0)
|
||||||
0028 GetLocal U32(0)
|
0028 GetLocal U32(0)
|
||||||
|
|||||||
@ -18,7 +18,7 @@ impl ContractRegistry {
|
|||||||
// GFX mappings
|
// GFX mappings
|
||||||
let mut gfx = HashMap::new();
|
let mut gfx = HashMap::new();
|
||||||
gfx.insert("clear".to_string(), ContractMethod {
|
gfx.insert("clear".to_string(), ContractMethod {
|
||||||
id: 0x1001,
|
id: 0x1010,
|
||||||
params: vec![PbsType::Struct("Color".to_string())],
|
params: vec![PbsType::Struct("Color".to_string())],
|
||||||
return_type: PbsType::Void,
|
return_type: PbsType::Void,
|
||||||
});
|
});
|
||||||
@ -62,12 +62,12 @@ impl ContractRegistry {
|
|||||||
// Input mappings
|
// Input mappings
|
||||||
let mut input = HashMap::new();
|
let mut input = HashMap::new();
|
||||||
input.insert("pad".to_string(), ContractMethod {
|
input.insert("pad".to_string(), ContractMethod {
|
||||||
id: 0x2001,
|
id: 0x2010,
|
||||||
params: vec![],
|
params: vec![],
|
||||||
return_type: PbsType::Struct("Pad".to_string()),
|
return_type: PbsType::Struct("Pad".to_string()),
|
||||||
});
|
});
|
||||||
input.insert("touch".to_string(), ContractMethod {
|
input.insert("touch".to_string(), ContractMethod {
|
||||||
id: 0x2002,
|
id: 0x2011,
|
||||||
params: vec![],
|
params: vec![],
|
||||||
return_type: PbsType::Struct("Touch".to_string()),
|
return_type: PbsType::Struct("Touch".to_string()),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::common::diagnostics::{Diagnostic, DiagnosticBundle, DiagnosticLevel};
|
|||||||
use crate::frontends::pbs::ast::*;
|
use crate::frontends::pbs::ast::*;
|
||||||
use crate::frontends::pbs::symbols::*;
|
use crate::frontends::pbs::symbols::*;
|
||||||
use crate::frontends::pbs::contracts::ContractRegistry;
|
use crate::frontends::pbs::contracts::ContractRegistry;
|
||||||
|
use crate::frontends::pbs::types::PbsType;
|
||||||
use crate::ir_core;
|
use crate::ir_core;
|
||||||
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
|
use crate::ir_core::ids::{FieldId, FunctionId, TypeId, ValueId};
|
||||||
use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type};
|
use crate::ir_core::{Block, Function, Instr, Module, Param, Program, Terminator, Type};
|
||||||
@ -126,7 +127,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
name: param.name.clone(),
|
name: param.name.clone(),
|
||||||
ty: ty.clone(),
|
ty: ty.clone(),
|
||||||
});
|
});
|
||||||
self.local_vars[0].insert(param.name.clone(), i as u32);
|
self.local_vars[0].insert(param.name.clone(), LocalInfo { slot: i as u32, ty: ty.clone() });
|
||||||
local_types.insert(i as u32, ty);
|
local_types.insert(i as u32, ty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,8 +185,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Node::BoundedLit(n) => {
|
Node::BoundedLit(n) => {
|
||||||
let id = self.program.const_pool.add_int(n.value as i64);
|
self.emit(Instr::PushBounded(n.value));
|
||||||
self.emit(Instr::PushConst(id));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Node::Ident(n) => self.lower_ident(n),
|
Node::Ident(n) => self.lower_ident(n),
|
||||||
@ -259,7 +259,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
|
|
||||||
// 2. Preserve gate identity
|
// 2. Preserve gate identity
|
||||||
let gate_slot = self.get_next_local_slot();
|
let gate_slot = self.get_next_local_slot();
|
||||||
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), LocalInfo { slot: gate_slot, ty: Type::Int });
|
||||||
self.emit(Instr::SetLocal(gate_slot));
|
self.emit(Instr::SetLocal(gate_slot));
|
||||||
|
|
||||||
// 3. Begin Operation
|
// 3. Begin Operation
|
||||||
@ -269,7 +269,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
// 4. Bind view to local
|
// 4. Bind view to local
|
||||||
self.local_vars.push(HashMap::new());
|
self.local_vars.push(HashMap::new());
|
||||||
let view_slot = self.get_next_local_slot();
|
let view_slot = self.get_next_local_slot();
|
||||||
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), view_slot);
|
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), LocalInfo { slot: view_slot, ty: Type::Int });
|
||||||
self.emit(Instr::SetLocal(view_slot));
|
self.emit(Instr::SetLocal(view_slot));
|
||||||
|
|
||||||
// 5. Body
|
// 5. Body
|
||||||
@ -288,7 +288,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
|
|
||||||
// 2. Preserve gate identity
|
// 2. Preserve gate identity
|
||||||
let gate_slot = self.get_next_local_slot();
|
let gate_slot = self.get_next_local_slot();
|
||||||
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), LocalInfo { slot: gate_slot, ty: Type::Int });
|
||||||
self.emit(Instr::SetLocal(gate_slot));
|
self.emit(Instr::SetLocal(gate_slot));
|
||||||
|
|
||||||
// 3. Begin Operation
|
// 3. Begin Operation
|
||||||
@ -298,7 +298,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
// 4. Bind view to local
|
// 4. Bind view to local
|
||||||
self.local_vars.push(HashMap::new());
|
self.local_vars.push(HashMap::new());
|
||||||
let view_slot = self.get_next_local_slot();
|
let view_slot = self.get_next_local_slot();
|
||||||
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), view_slot);
|
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), LocalInfo { slot: view_slot, ty: Type::Int });
|
||||||
self.emit(Instr::SetLocal(view_slot));
|
self.emit(Instr::SetLocal(view_slot));
|
||||||
|
|
||||||
// 5. Body
|
// 5. Body
|
||||||
@ -317,7 +317,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
|
|
||||||
// 2. Preserve gate identity
|
// 2. Preserve gate identity
|
||||||
let gate_slot = self.get_next_local_slot();
|
let gate_slot = self.get_next_local_slot();
|
||||||
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), gate_slot);
|
self.local_vars.last_mut().unwrap().insert(format!("$gate_{}", gate_slot), LocalInfo { slot: gate_slot, ty: Type::Int });
|
||||||
self.emit(Instr::SetLocal(gate_slot));
|
self.emit(Instr::SetLocal(gate_slot));
|
||||||
|
|
||||||
// 3. Begin Operation
|
// 3. Begin Operation
|
||||||
@ -327,7 +327,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
// 4. Bind view to local
|
// 4. Bind view to local
|
||||||
self.local_vars.push(HashMap::new());
|
self.local_vars.push(HashMap::new());
|
||||||
let view_slot = self.get_next_local_slot();
|
let view_slot = self.get_next_local_slot();
|
||||||
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), view_slot);
|
self.local_vars.last_mut().unwrap().insert(n.binding.to_string(), LocalInfo { slot: view_slot, ty: Type::Int });
|
||||||
self.emit(Instr::SetLocal(view_slot));
|
self.emit(Instr::SetLocal(view_slot));
|
||||||
|
|
||||||
// 5. Body
|
// 5. Body
|
||||||
@ -449,8 +449,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let id = self.program.const_pool.add_int(val);
|
self.emit(Instr::PushBounded(val));
|
||||||
self.emit(Instr::PushConst(id));
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -574,6 +573,12 @@ impl<'a> Lowerer<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for .raw()
|
||||||
|
if ma.member == "raw" {
|
||||||
|
self.lower_node(&ma.object)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
// Check for Color.rgb
|
// Check for Color.rgb
|
||||||
if ma.member == "rgb" {
|
if ma.member == "rgb" {
|
||||||
if let Node::Ident(obj_id) = &*ma.object {
|
if let Node::Ident(obj_id) = &*ma.object {
|
||||||
@ -582,7 +587,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
// Try to get literal values for r, g, b
|
// Try to get literal values for r, g, b
|
||||||
let mut literals = Vec::new();
|
let mut literals = Vec::new();
|
||||||
for arg in &n.args {
|
for arg in &n.args {
|
||||||
if let Node::IntLiteral(lit) = arg {
|
if let Node::IntLit(lit) = arg {
|
||||||
literals.push(Some(lit.value));
|
literals.push(Some(lit.value));
|
||||||
} else {
|
} else {
|
||||||
literals.push(None);
|
literals.push(None);
|
||||||
@ -594,8 +599,7 @@ impl<'a> Lowerer<'a> {
|
|||||||
let g6 = (g & 0xFF) >> 2;
|
let g6 = (g & 0xFF) >> 2;
|
||||||
let b5 = (b & 0xFF) >> 3;
|
let b5 = (b & 0xFF) >> 3;
|
||||||
let rgb565 = (r5 << 11) | (g6 << 5) | b5;
|
let rgb565 = (r5 << 11) | (g6 << 5) | b5;
|
||||||
let id = self.program.const_pool.add_int(rgb565);
|
self.emit(Instr::PushBounded(rgb565 as u32));
|
||||||
self.emit(Instr::PushConst(id));
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -615,8 +619,10 @@ impl<'a> Lowerer<'a> {
|
|||||||
let is_shadowed = self.find_local(&obj_id.name).is_some();
|
let is_shadowed = self.find_local(&obj_id.name).is_some();
|
||||||
|
|
||||||
if is_host_contract && !is_shadowed {
|
if is_host_contract && !is_shadowed {
|
||||||
if let Some(syscall_id) = self.contract_registry.resolve(&obj_id.name, &ma.member) {
|
if let Some(method) = self.contract_registry.get_method(&obj_id.name, &ma.member) {
|
||||||
self.emit(Instr::HostCall(syscall_id));
|
let ir_ty = self.convert_pbs_type(&method.return_type);
|
||||||
|
let return_slots = self.get_type_slots(&ir_ty);
|
||||||
|
self.emit(Instr::HostCall(method.id, return_slots));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span);
|
self.error("E_RESOLVE_UNDEFINED", format!("Undefined contract member '{}.{}'", obj_id.name, ma.member), ma.span);
|
||||||
@ -810,11 +816,37 @@ impl<'a> Lowerer<'a> {
|
|||||||
|
|
||||||
fn get_type_slots(&self, ty: &Type) -> u32 {
|
fn get_type_slots(&self, ty: &Type) -> u32 {
|
||||||
match ty {
|
match ty {
|
||||||
|
Type::Void => 0,
|
||||||
Type::Struct(name) => self.struct_slots.get(name).cloned().unwrap_or(1),
|
Type::Struct(name) => self.struct_slots.get(name).cloned().unwrap_or(1),
|
||||||
Type::Array(_, size) => *size,
|
Type::Array(_, size) => *size,
|
||||||
_ => 1,
|
_ => 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn convert_pbs_type(&self, ty: &PbsType) -> Type {
|
||||||
|
match ty {
|
||||||
|
PbsType::Int => Type::Int,
|
||||||
|
PbsType::Float => Type::Float,
|
||||||
|
PbsType::Bool => Type::Bool,
|
||||||
|
PbsType::String => Type::String,
|
||||||
|
PbsType::Void => Type::Void,
|
||||||
|
PbsType::None => Type::Void,
|
||||||
|
PbsType::Bounded => Type::Bounded,
|
||||||
|
PbsType::Optional(inner) => Type::Optional(Box::new(self.convert_pbs_type(inner))),
|
||||||
|
PbsType::Result(ok, err) => Type::Result(
|
||||||
|
Box::new(self.convert_pbs_type(ok)),
|
||||||
|
Box::new(self.convert_pbs_type(err)),
|
||||||
|
),
|
||||||
|
PbsType::Struct(name) => Type::Struct(name.clone()),
|
||||||
|
PbsType::Service(name) => Type::Service(name.clone()),
|
||||||
|
PbsType::Contract(name) => Type::Contract(name.clone()),
|
||||||
|
PbsType::ErrorType(name) => Type::ErrorType(name.clone()),
|
||||||
|
PbsType::Function { params, return_type } => Type::Function {
|
||||||
|
params: params.iter().map(|p| self.convert_pbs_type(p)).collect(),
|
||||||
|
return_type: Box::new(self.convert_pbs_type(return_type)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -1013,10 +1045,10 @@ mod tests {
|
|||||||
let func = &program.modules[0].functions[0];
|
let func = &program.modules[0].functions[0];
|
||||||
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
let instrs: Vec<_> = func.blocks.iter().flat_map(|b| b.instrs.iter()).collect();
|
||||||
|
|
||||||
// Gfx.clear -> 0x1001
|
// Gfx.clear -> 0x1010
|
||||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x1001))));
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x1010, 0))));
|
||||||
// Log.write -> 0x5001
|
// Log.write -> 0x5001
|
||||||
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x5001))));
|
assert!(instrs.iter().any(|i| matches!(i, ir_core::Instr::HostCall(0x5001, 0))));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -614,6 +614,20 @@ impl<'a> TypeChecker<'a> {
|
|||||||
if expected == found {
|
if expected == found {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Color is basically a bounded (u16)
|
||||||
|
if matches!(expected, PbsType::Struct(s) if s == "Color") && *found == PbsType::Bounded {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if *expected == PbsType::Bounded && matches!(found, PbsType::Struct(s) if s == "Color") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow int as Color/bounded (for compatibility)
|
||||||
|
if (matches!(expected, PbsType::Struct(s) if s == "Color") || *expected == PbsType::Bounded) && *found == PbsType::Int {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
match (expected, found) {
|
match (expected, found) {
|
||||||
(PbsType::Optional(_), PbsType::None) => true,
|
(PbsType::Optional(_), PbsType::None) => true,
|
||||||
(PbsType::Optional(inner), found) => self.is_assignable(inner, found),
|
(PbsType::Optional(inner), found) => self.is_assignable(inner, found),
|
||||||
|
|||||||
@ -6,10 +6,12 @@ use super::ids::{ConstId, FieldId, FunctionId, TypeId, ValueId};
|
|||||||
pub enum Instr {
|
pub enum Instr {
|
||||||
/// Placeholder for constant loading.
|
/// Placeholder for constant loading.
|
||||||
PushConst(ConstId),
|
PushConst(ConstId),
|
||||||
|
/// Push a bounded value (0..0xFFFF).
|
||||||
|
PushBounded(u32),
|
||||||
/// Placeholder for function calls.
|
/// Placeholder for function calls.
|
||||||
Call(FunctionId, u32),
|
Call(FunctionId, u32),
|
||||||
/// Host calls (syscalls).
|
/// Host calls (syscalls). (id, return_slots)
|
||||||
HostCall(u32),
|
HostCall(u32, u32),
|
||||||
/// Variable access.
|
/// Variable access.
|
||||||
GetLocal(u32),
|
GetLocal(u32),
|
||||||
SetLocal(u32),
|
SetLocal(u32),
|
||||||
|
|||||||
@ -44,6 +44,8 @@ pub enum InstrKind {
|
|||||||
|
|
||||||
/// Pushes a constant from the pool onto the stack.
|
/// Pushes a constant from the pool onto the stack.
|
||||||
PushConst(ConstId),
|
PushConst(ConstId),
|
||||||
|
/// Pushes a bounded value (0..0xFFFF) onto the stack.
|
||||||
|
PushBounded(u32),
|
||||||
/// Pushes a boolean onto the stack.
|
/// Pushes a boolean onto the stack.
|
||||||
PushBool(bool),
|
PushBool(bool),
|
||||||
/// Pushes a `null` value onto the stack.
|
/// Pushes a `null` value onto the stack.
|
||||||
@ -203,6 +205,7 @@ mod tests {
|
|||||||
InstrKind::Nop,
|
InstrKind::Nop,
|
||||||
InstrKind::Halt,
|
InstrKind::Halt,
|
||||||
InstrKind::PushConst(ConstId(0)),
|
InstrKind::PushConst(ConstId(0)),
|
||||||
|
InstrKind::PushBounded(0),
|
||||||
InstrKind::PushBool(true),
|
InstrKind::PushBool(true),
|
||||||
InstrKind::PushNull,
|
InstrKind::PushNull,
|
||||||
InstrKind::Pop,
|
InstrKind::Pop,
|
||||||
@ -261,6 +264,9 @@ mod tests {
|
|||||||
{
|
{
|
||||||
"PushConst": 0
|
"PushConst": 0
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"PushBounded": 0
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"PushBool": true
|
"PushBool": true
|
||||||
},
|
},
|
||||||
|
|||||||
@ -92,6 +92,10 @@ pub fn lower_function(
|
|||||||
stack_types.push(ty);
|
stack_types.push(ty);
|
||||||
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushConst(ir_vm::ConstId(id.0)), None));
|
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushConst(ir_vm::ConstId(id.0)), None));
|
||||||
}
|
}
|
||||||
|
ir_core::Instr::PushBounded(val) => {
|
||||||
|
stack_types.push(ir_core::Type::Bounded);
|
||||||
|
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::PushBounded(*val), None));
|
||||||
|
}
|
||||||
ir_core::Instr::Call(func_id, arg_count) => {
|
ir_core::Instr::Call(func_id, arg_count) => {
|
||||||
// Pop arguments from type stack
|
// Pop arguments from type stack
|
||||||
for _ in 0..*arg_count {
|
for _ in 0..*arg_count {
|
||||||
@ -106,10 +110,12 @@ pub fn lower_function(
|
|||||||
arg_count: *arg_count
|
arg_count: *arg_count
|
||||||
}, None));
|
}, None));
|
||||||
}
|
}
|
||||||
ir_core::Instr::HostCall(id) => {
|
ir_core::Instr::HostCall(id, slots) => {
|
||||||
// HostCall return types are not easily known without a registry,
|
// HostCall return types are not easily known without a registry,
|
||||||
// but usually they return Int or Void in v0.
|
// but we now pass the number of slots.
|
||||||
|
for _ in 0..*slots {
|
||||||
stack_types.push(ir_core::Type::Int);
|
stack_types.push(ir_core::Type::Int);
|
||||||
|
}
|
||||||
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Syscall(*id), None));
|
vm_func.body.push(ir_vm::Instruction::new(ir_vm::InstrKind::Syscall(*id), None));
|
||||||
}
|
}
|
||||||
ir_core::Instr::GetLocal(slot) => {
|
ir_core::Instr::GetLocal(slot) => {
|
||||||
@ -366,6 +372,7 @@ fn lower_type(ty: &ir_core::Type) -> ir_vm::Type {
|
|||||||
ir_core::Type::Float => ir_vm::Type::Float,
|
ir_core::Type::Float => ir_vm::Type::Float,
|
||||||
ir_core::Type::Bool => ir_vm::Type::Bool,
|
ir_core::Type::Bool => ir_vm::Type::Bool,
|
||||||
ir_core::Type::String => ir_vm::Type::String,
|
ir_core::Type::String => ir_vm::Type::String,
|
||||||
|
ir_core::Type::Bounded => ir_vm::Type::Bounded,
|
||||||
ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))),
|
ir_core::Type::Optional(inner) => ir_vm::Type::Array(Box::new(lower_type(inner))),
|
||||||
ir_core::Type::Result(ok, _) => lower_type(ok),
|
ir_core::Type::Result(ok, _) => lower_type(ok),
|
||||||
ir_core::Type::Struct(_)
|
ir_core::Type::Struct(_)
|
||||||
@ -411,7 +418,7 @@ mod tests {
|
|||||||
Block {
|
Block {
|
||||||
id: 1,
|
id: 1,
|
||||||
instrs: vec![
|
instrs: vec![
|
||||||
Instr::HostCall(42),
|
Instr::HostCall(42, 1),
|
||||||
],
|
],
|
||||||
terminator: Terminator::Return,
|
terminator: Terminator::Return,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -710,6 +710,55 @@ mod tests {
|
|||||||
_ => panic!("Expected Trap"),
|
_ => panic!("Expected Trap"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_gfx_clear565_syscall() {
|
||||||
|
let mut hw = crate::Hardware::new();
|
||||||
|
let mut os = PrometeuOS::new(None);
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
|
||||||
|
// Success case
|
||||||
|
let args = vec![Value::Bounded(0xF800)]; // Red
|
||||||
|
{
|
||||||
|
let mut ret = HostReturn::new(&mut stack);
|
||||||
|
os.syscall(Syscall::GfxClear565 as u32, &args, &mut ret, &mut hw).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(stack.len(), 0); // void return
|
||||||
|
|
||||||
|
// OOB case
|
||||||
|
let args = vec![Value::Bounded(0x10000)];
|
||||||
|
{
|
||||||
|
let mut ret = HostReturn::new(&mut stack);
|
||||||
|
let res = os.syscall(Syscall::GfxClear565 as u32, &args, &mut ret, &mut hw);
|
||||||
|
assert!(res.is_err());
|
||||||
|
match res.err().unwrap() {
|
||||||
|
VmFault::Trap(trap, _) => assert_eq!(trap, prometeu_bytecode::abi::TRAP_OOB),
|
||||||
|
_ => panic!("Expected Trap OOB"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_input_snapshots_syscalls() {
|
||||||
|
let mut hw = crate::Hardware::new();
|
||||||
|
let mut os = PrometeuOS::new(None);
|
||||||
|
|
||||||
|
// Pad snapshot
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
{
|
||||||
|
let mut ret = HostReturn::new(&mut stack);
|
||||||
|
os.syscall(Syscall::InputPadSnapshot as u32, &[], &mut ret, &mut hw).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(stack.len(), 48);
|
||||||
|
|
||||||
|
// Touch snapshot
|
||||||
|
let mut stack = Vec::new();
|
||||||
|
{
|
||||||
|
let mut ret = HostReturn::new(&mut stack);
|
||||||
|
os.syscall(Syscall::InputTouchSnapshot as u32, &[], &mut ret, &mut hw).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(stack.len(), 6);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeInterface for PrometeuOS {
|
impl NativeInterface for PrometeuOS {
|
||||||
@ -861,6 +910,17 @@ impl NativeInterface for PrometeuOS {
|
|||||||
ret.push_null();
|
ret.push_null();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
// gfx.clear565(color_u16) -> void
|
||||||
|
Syscall::GfxClear565 => {
|
||||||
|
let color_val = expect_int(args, 0)? as u32;
|
||||||
|
if color_val > 0xFFFF {
|
||||||
|
return Err(VmFault::Trap(prometeu_bytecode::abi::TRAP_OOB, "Color value out of bounds (bounded)".into()));
|
||||||
|
}
|
||||||
|
let color = Color::from_raw(color_val as u16);
|
||||||
|
hw.gfx_mut().clear(color);
|
||||||
|
// No return value for void
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// --- Input Syscalls ---
|
// --- Input Syscalls ---
|
||||||
|
|
||||||
@ -914,6 +974,30 @@ impl NativeInterface for PrometeuOS {
|
|||||||
ret.push_int(hw.touch().f.hold_frames as i64);
|
ret.push_int(hw.touch().f.hold_frames as i64);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Syscall::InputPadSnapshot => {
|
||||||
|
let pad = hw.pad();
|
||||||
|
for btn in [
|
||||||
|
&pad.up, &pad.down, &pad.left, &pad.right,
|
||||||
|
&pad.a, &pad.b, &pad.x, &pad.y,
|
||||||
|
&pad.l, &pad.r, &pad.start, &pad.select,
|
||||||
|
] {
|
||||||
|
ret.push_bool(btn.pressed);
|
||||||
|
ret.push_bool(btn.released);
|
||||||
|
ret.push_bool(btn.down);
|
||||||
|
ret.push_int(btn.hold_frames as i64);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Syscall::InputTouchSnapshot => {
|
||||||
|
let touch = hw.touch();
|
||||||
|
ret.push_bool(touch.f.pressed);
|
||||||
|
ret.push_bool(touch.f.released);
|
||||||
|
ret.push_bool(touch.f.down);
|
||||||
|
ret.push_int(touch.f.hold_frames as i64);
|
||||||
|
ret.push_int(touch.x as i64);
|
||||||
|
ret.push_int(touch.y as i64);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// --- Audio Syscalls ---
|
// --- Audio Syscalls ---
|
||||||
|
|
||||||
|
|||||||
@ -1,28 +0,0 @@
|
|||||||
> **Status:** Ready to copy/paste to Junie
|
|
||||||
>
|
|
||||||
> **Goal:** expose hardware types **1:1** to PBS and VM as **SAFE builtins** (stack/value), *not* HIP.
|
|
||||||
>
|
|
||||||
> **Key constraint:** Prometeu does **not** have `u16` as a primitive. Use `bounded` for 16-bit-ish hardware scalars.
|
|
||||||
>
|
|
||||||
> **Deliverables (in order):**
|
|
||||||
>
|
|
||||||
> 1. VM hostcall ABI supports returning **flattened SAFE structs** (multi-slot).
|
|
||||||
> 2. PBS prelude defines `Color`, `ButtonState`, `Pad`, `Touch` using `bounded`.
|
|
||||||
> 3. Lowering emits deterministic syscalls for `Gfx.clear(Color)` and `Input.pad()/touch()`.
|
|
||||||
> 4. Runtime implements the syscalls and an integration cartridge validates behavior.
|
|
||||||
>
|
|
||||||
> **Hard rules (do not break):**
|
|
||||||
>
|
|
||||||
> * No heap, no gates, no HIP for these types.
|
|
||||||
> * No `u16` anywhere in PBS surface.
|
|
||||||
> * Returned structs are *values*, copied by stack.
|
|
||||||
> * Every PR must include tests.
|
|
||||||
> * No renumbering opcodes; append only.
|
|
||||||
|
|
||||||
## Notes / Forbidden
|
|
||||||
|
|
||||||
* DO NOT introduce `u16` into PBS.
|
|
||||||
* DO NOT allocate heap for these types.
|
|
||||||
* DO NOT encode `Pad`/`Touch` as gates.
|
|
||||||
* DO NOT change unrelated opcodes.
|
|
||||||
* DO NOT add “convenient” APIs not listed above.
|
|
||||||
@ -1,145 +0,0 @@
|
|||||||
## PR-03 — Lowering: Host Contracts for Gfx/Input using deterministic syscalls
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Map PBS host contracts to stable syscalls with a deterministic ABI.
|
|
||||||
|
|
||||||
### Required host contracts in PBS surface
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
pub declare contract Gfx host
|
|
||||||
{
|
|
||||||
fn clear(color: Color): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub declare contract Input host
|
|
||||||
{
|
|
||||||
fn pad(): Pad;
|
|
||||||
fn touch(): Touch;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required lowering rules
|
|
||||||
|
|
||||||
1. `Gfx.clear(color)`
|
|
||||||
|
|
||||||
* Emit `SYSCALL_GFX_CLEAR`
|
|
||||||
* ABI: args = [Color.raw] as `bounded`
|
|
||||||
* returns: void
|
|
||||||
|
|
||||||
2. `Input.pad()`
|
|
||||||
|
|
||||||
* Emit `SYSCALL_INPUT_PAD`
|
|
||||||
* args: none
|
|
||||||
* returns: flattened `Pad` in field order as declared
|
|
||||||
|
|
||||||
3. `Input.touch()`
|
|
||||||
|
|
||||||
* Emit `SYSCALL_INPUT_TOUCH`
|
|
||||||
* args: none
|
|
||||||
* returns: flattened `Touch` in field order as declared
|
|
||||||
|
|
||||||
### Flattening order (binding)
|
|
||||||
|
|
||||||
**ButtonState** returns 4 slots in order:
|
|
||||||
|
|
||||||
1. pressed (bool)
|
|
||||||
2. released (bool)
|
|
||||||
3. down (bool)
|
|
||||||
4. hold_frames (bounded)
|
|
||||||
|
|
||||||
**Pad** returns 12 ButtonState blocks in this exact order:
|
|
||||||
`up, down, left, right, a, b, x, y, l, r, start, select`
|
|
||||||
|
|
||||||
**Touch** returns:
|
|
||||||
|
|
||||||
1. f (ButtonState block)
|
|
||||||
2. x (int)
|
|
||||||
3. y (int)
|
|
||||||
|
|
||||||
### Tests (mandatory)
|
|
||||||
|
|
||||||
* Lowering golden test: `Gfx.clear(Color.WHITE)` emits `SYSCALL_GFX_CLEAR` with 1 arg.
|
|
||||||
* Lowering golden test: `Input.pad()` emits `SYSCALL_INPUT_PAD` and assigns to local.
|
|
||||||
* Lowering golden test: `Input.touch()` emits `SYSCALL_INPUT_TOUCH`.
|
|
||||||
|
|
||||||
### Non-goals
|
|
||||||
|
|
||||||
* No runtime changes
|
|
||||||
* No VM heap
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PR-04 — Runtime: Implement syscalls for Color/Gfx and Input pad/touch + integration cartridge
|
|
||||||
|
|
||||||
### Goal
|
|
||||||
|
|
||||||
Make the new syscalls actually work and prove them with an integration test cartridge.
|
|
||||||
|
|
||||||
### Required syscall implementations
|
|
||||||
|
|
||||||
#### 1) `SYSCALL_GFX_CLEAR`
|
|
||||||
|
|
||||||
* Read 1 arg: `bounded` raw color
|
|
||||||
* Convert to `u16` internally (runtime-only)
|
|
||||||
|
|
||||||
* If raw > 0xFFFF, trap `TRAP_OOB` or `TRAP_TYPE` (choose one and document)
|
|
||||||
* Fill framebuffer with that RGB565 value
|
|
||||||
|
|
||||||
#### 2) `SYSCALL_INPUT_PAD`
|
|
||||||
|
|
||||||
* No args
|
|
||||||
* Snapshot the current runtime `Pad` and push a flattened `Pad` return:
|
|
||||||
|
|
||||||
* For each button: pressed, released, down, hold_frames
|
|
||||||
* hold_frames pushed as `bounded`
|
|
||||||
|
|
||||||
#### 3) `SYSCALL_INPUT_TOUCH`
|
|
||||||
|
|
||||||
* No args
|
|
||||||
* Snapshot `Touch` and push flattened `Touch` return:
|
|
||||||
|
|
||||||
* f ButtonState
|
|
||||||
* x int
|
|
||||||
* y int
|
|
||||||
|
|
||||||
### Integration cartridge (mandatory)
|
|
||||||
|
|
||||||
Add `test-cartridges/hw_hello` (or similar) with:
|
|
||||||
|
|
||||||
```pbs
|
|
||||||
fn frame(): void
|
|
||||||
{
|
|
||||||
// 1) clear screen white
|
|
||||||
Gfx.clear(Color.WHITE);
|
|
||||||
|
|
||||||
// 2) read pad and branch
|
|
||||||
let p: Pad = Input.pad();
|
|
||||||
if p.any() {
|
|
||||||
Gfx.clear(Color.MAGENTA);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) read touch and branch on f.down
|
|
||||||
let t: Touch = Input.touch();
|
|
||||||
if t.f.down {
|
|
||||||
// choose a third color to prove the struct returned correctly
|
|
||||||
Gfx.clear(Color.BLUE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Acceptance criteria
|
|
||||||
|
|
||||||
* Cartridge runs without VM faults.
|
|
||||||
* With no input: screen is WHITE.
|
|
||||||
* With any pad button held: screen becomes MAGENTA.
|
|
||||||
* With touch f.down: screen becomes BLUE.
|
|
||||||
|
|
||||||
### Tests (mandatory)
|
|
||||||
|
|
||||||
* Runtime unit test: `SYSCALL_GFX_CLEAR` rejects raw > 0xFFFF deterministically.
|
|
||||||
* Runtime unit test: `SYSCALL_INPUT_PAD` returns correct number of stack slots (48).
|
|
||||||
* Runtime unit test: `SYSCALL_INPUT_TOUCH` returns correct number of stack slots (4 + 2 = 6).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
14
test-cartridges/hw_hello/src/main.pbs
Normal file
14
test-cartridges/hw_hello/src/main.pbs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
fn frame(): void
|
||||||
|
{
|
||||||
|
Gfx.clear(Color.WHITE);
|
||||||
|
|
||||||
|
let p: Pad = Input.pad();
|
||||||
|
if p.any() {
|
||||||
|
Gfx.clear(Color.MAGENTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
let t: Touch = Input.touch();
|
||||||
|
if t.f.down {
|
||||||
|
Gfx.clear(Color.BLUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user